https://wiki.alliedmods.net/api.php?action=feedcontributions&user=Fredd&feedformat=atomAlliedModders Wiki - User contributions [en]2024-03-28T19:35:50ZUser contributionsMediaWiki 1.31.6https://wiki.alliedmods.net/index.php?title=Cross_Compiling_Plugins_(Metamod:Source)&diff=5809Cross Compiling Plugins (Metamod:Source)2008-05-14T22:14:59Z<p>Fredd: /* Installing CrossTool */</p>
<hr />
<div>==Introduction==<br />
The aim of this tutorial is to allow Windows Half-Life 2 plugin coders to compile their plugins on the same machine as the windows binaries.<br />
The binaries are compatible with 99% of all linux servers and do not suffer from bulky file sizes.<br />
One of the main benefits to using this method of compiling is that your code can be left wherever you currently have it so that compile time reluctance is removed and coders can concentrate on coding the plugins instead of "How on earth am I to compile this for linux ???".<br />
All you need to get started are the base Cygwin installation, the CrossTool.tar.gz file and the makefile. Links to the latter two are included at the end of the tutorial.<br />
<br />
This tutorial assumes the following.<br />
<OL><br />
<LI>You currently have a plugin which you can compile on windows using MSVC.<br />
<LI>You have a copy of the HL2SDK Source Code installed on your hard drive.<br />
<LI>You have a copy of the [http://www.sourcemm.net Metamod:Source] source code on your hard drive.<br />
</OL><br />
<br />
<br />
Although the cross compiler will allow for compiling of Valves standard plugins, I do not include a Makefile or instructions on how to successfully accomplish this.<br />
<br />
==Installing Cygwin==<br />
First, download CygWin from [http://www.cygwin.com www.CygWin.com] by clicking on "Install Now" located in the middle of the page.<br />
<br />
Once the download finishes, run <i>setup.exe</i><br />
Click 'Next' on the introduction screen.<br />
<br />
On the second page, select 'Install from Internet'.<br><br />
The following page is all user preferances, except the 'Default Text File Type' which is recommended you use 'Unix / binary'.<br><br><br />
After clicking next, you are prompted for a CygWin temporary folder, any folder will suffice, the default is usually the better option.<br><br />
The next screen's options are firewall/proxy settings, these are specific to the Computer/Network you are currently using.<br><br />
Once you click next on the previous screen, the installer will download a list of mirror sites containing the CygWin binaries. Select one which you think is closest to you for a faster installation time.<br><br><br />
Finaly, you are presented with a 'Select Packages' screen.<br><br />
I *think* it should work fine without all of the dev packages.<br><br><br />
<br />
If anyone has gotten this to work by selecting less packages, please let me know so I can remove them from this list.<br><br />
Items marked in bold are extremely important.<br><br><br />
<br />
*binutils<br />
*<b>bzip2</b><br />
*cygutils<br />
*gcc<br />
*<b>glib</b><br />
*<b>gzip</b><br />
<br />
*libiconv<br />
*<b>make</b><br />
*<b>zlib</b><br />
*<b>grep</b><br />
<br />
Click next and it should install the packages to the target path you specified.<br />
<p><br />
<br />
==Installing CrossTool==<br />
Download the CrossCompiler from one of the mirrors below to the root cygwin folder <i>(c:\cygwin)</i>.<br />
If it gets renamed to 'crosstool_gcc-3.4.1.tar.tar' please rename it to 'crosstool_gcc-3.4.1.tar.gz'.<br />
Download Mirrors for 'crosstool_gcc.tar.gz'<br />
*[http://files.filefront.com/crosstool+gcc+341targz/;9813824;/fileinfo.html FileFront]<br />
*[http://downloads.punkassfraggers.com/redirect.php?dlid=1224 PunkAssFraggers.com]<br />
*[http://harrisonpham.com/sourcemods/crosstool/crosstool_gcc-3.4.1.tar.gz http://harrisonpham.com] -> Thanks to el hippo<br />
<br />
This is the most important part of the entire tutorial.<br />
<br />
<b>Do not try and extract 'crosstool_gcc-3.4.1.tar.gz' using any Windows Archiving utility !</b><br />
They do not extract the files correctly and if you don't get errors during the extraction, you will while trying to compile !<br />
Once the download is complete, run cygwin by navigating to the cygwin root folder, and double clicking on <i>'cygwin.bat'</i>.<br />
Type the following two commands in order into the cygwin bash shell followed by the enter key:<br />
<br />
cd /<br />
tar -xzf crosstool_gcc-3.4.1.tar.gz<br />
<br />
This will start extracting the tar file to the '<cygroot>/opt' folder on your hard drive.<br />
And thats it, now you have the pre-compiled GCC 3.4.1 [http://www.lduke.com (Thanks LDuke)] installed on your computer you can compile server plugins for both regular and SourceMM flavours with ease and compatibility with Valves GCC 3.4.1 requirements.<br />
<p><br />
<br />
==Compiling Plugins==<br />
Download the Makefile by clicking on [http://www.c0ld.net/Tutorials/CrossTool/Makefile this link.]<br />
<br />
Save it to your project's root source folder and open it with a text editor like notepad.<br><br />
Inside the Makefile I have shown examples on how your paths are translated into unix/cygwin hybrid paths.<br><br />
So, before trying any compling just yet, you need to insert the paths for the following.<br><br />
<br />
<b>HL2SDK</b>: The path to where your HL2SDK files are located.<br><br />
<b>SMM_ROOT</b>: The path to where you extracted the contents of the [http://www.sourcemm.net/ SourceMM package]<br><br />
<b>SRCDS</b>: The path to the linux SRCDS binaries.<br><br />
<b>PLUGIN</b>: The plugin filename.<br><br><br />
<br />
For the convenience of those who don't have access to a Linux srcds installation, you can grab the files required at one of the mirrors below.<br><br />
Extract them to a new folder which will be your SRCDS path in the Makefile.<br><br />
<b>Note:</b>The bin folder must be inside the srcds folder for maximum compatibility. For example, your files end up being something like this 'C:\MyFiles\srcds_l\bin\vstdlib_i486.so'.<br />
<br />
So, in your Makefile, SRCDS would be '/cygdrive/c/MyFiles/srcds_l'<br />
<br />
Download Mirrors for 'srcds_l_binaries.zip'<br />
*[http://files.filefront.com/srcds_l_binarieszip/;4651213;;/fileinfo.html FileFront Mirror]<br />
*[http://rapidshare.de/files/11597264/srcds_l_binaries.zip.html RapidShare.de Mirror]<br />
Now you're ready to try compiling.<br />
Back to the cygwin bash window, and we're now going to have to navigate to your code's location.<br />
An example path could be 'C:\MyCode\MyPlugin\' so in the bash shell we type the following:<br />
<br />
cd /cygdrive/c/MyCode/MyPlugin/<br />
make<br />
<br />
If all your paths are set correctly, it should start compiling the plugin.<br />
Ignore all the warnings in the HL2SDK, they are harmless.<br />
What you should be looking out for are errors and warnings located inside your own code.<br />
Unfortunately I cannot compile plugins for you, but there is a forum dedicated to helping you with hl2sdk coding problems.<br />
<br />
[http://forums.alliedmods.net/forumdisplay.php?f=75 Click here to visit the forum]<br />
<br />
==Credits==<br />
*Tutorial by: Jason "c0ldfyr3" Croghan<br />
*Cross Compiler compiled by: L. Duke<br />
*Makefile originally written by: David "BAILOPAN" Anderson<br />
*Converted to Wiki by: James "sslice" Gray<br />
<br />
Original Location: http://www.c0ld.net/index.php?inc=CrossTool<br />
<br />
[[Category:Metamod:Source Development]]</div>Freddhttps://wiki.alliedmods.net/index.php?title=Menu_API_(SourceMod)&diff=5736Menu API (SourceMod)2008-04-20T00:09:15Z<p>Fredd: /* Advanced Voting */ variable miss spelled, was used as in "iteminfo" but it was named "item_info" in the function args..</p>
<hr />
<div>SourceMod has an extensive API for building and displaying menus to clients. Unlike AMX Mod X, this API is highly state driven. Menus are based on callbacks which are guaranteed to be fired.<br />
<br />
For C++, the Menu API can be found in <tt>public/IMenuManager.h</tt>. For SourcePawn, it is in <tt>plugins/include/menus.inc</tt>.<br />
<br />
=Objects=<br />
The SourceMod Menu System is based on an object oriented hierarchy. Understanding this hierarchy, even for scripting, is critical to using menus effectively.<br />
<br />
==Styles==<br />
The top level object is a ''MenuStyle'' (<tt>IMenuStyle</tt> in C++). Styles describe a unique menu system. There are two such styles built into SourceMod:<br />
*Valve Style, also called "ESC" menus; 8 items per page, no raw/disabled text can be rendered<br />
*Radio Style, also called "AMX" menus; 10 items per page, raw/disabled text can be rendered<br />
<br />
Each MenuStyle has its own rules and properties. You can think of them as existing on separate "channels." For example, two different menus can exist on a player's screen as both a Valve menu and a Radio menu at the same time, and SourceMod will be able to manage both without any problems. This is because each style keeps track of its own menus separately.<br />
<br />
==Panels==<br />
Menu displays are drawn with a lower level interface called ''Panels'' (<tt>IMenuPanel</tt> in C++). Panels describe exactly one chunk of display text. Both selectable items and raw text can be added to a panel as long as its parent style supports the contents you're trying to draw. For example, the Valve style does not support drawing raw text or disabled items. But with a Radio-style Panel, you can display a large amount of on-screen data in your own format.<br />
<br />
Panels are considered temporary objects. They are created, rendered, displayed, and destroyed. Although they can be saved indefinitely, it is not necessary to do so.<br />
<br />
Valve Style drawing rules/limitations:<br />
*Max items per page is 8.<br />
*Disabled items cannot be drawn.<br />
*Raw text cannot be drawn.<br />
*Spacers do not add a space/newline, giving a "cramped" feel.<br />
*Users must press "ESC" or be at their console to view the menu.<br />
<br />
Radio Style drawing rules/limitations:<br />
*Max items per page is 10.<br />
*Titles appear white; items appear yellow, unless disabled, in which case they are white.<br />
*The 0th item is always white. For consistency, this means navigational controls explained in the next section are always white, and simply not drawn if disabled.<br />
<br />
==Menus==<br />
Lastly, there are plain ''Menus'' (<tt>IBaseMenu</tt> in C++). These are helper objects designed for storing a menu based on selectable items. Unlike low-level panels, menus are containers for '''items''', and can only contain items which are selectable (i.e., do not contain raw text). They fall into two categories:<br />
*Non-paginated: The menu can only have a certain number of items on it, and no control/navigation options will be added, except for an "Exit" button which will always be in the last position supported by the style.<br />
**Valve Style maximum items: 8<br />
**Radio Style maximum items: 10<br />
*Paginated: The menu can have any number of items. When displayed, only a certain number of items will be drawn at a time. Automatic navigation controls are added so players can easily move back and forth to different "pages" of items in the menu.<br />
**"Previous" is always drawn as the first navigation item, third from the last supported position. This will not be drawn if the menu only contains one page. If there are no previous pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.<br />
***Valve Style position: 6<br />
***Radio Style position: 8<br />
**"Next" is always drawn as the second navigation item, second from the last supported position. This will not be drawn if the menu only contains one page. If there are no further pages, the text will not be drawn on either style; if possible, the menu will be padded so spacing is consistent.<br />
***Valve Style position: 7<br />
***Radio Style position: 9<br />
**"Exit" is drawn if the menu has the exit button property set. It is always the last supported item position.<br />
***Valve Style position: 8<br />
***Radio Style position: 10<br />
<br />
The purpose of Menus is to simplify the procedure of storing, drawing, and calculating the selection of items. Thus, menus do not allow for adding raw text, as that would considerably complicate the drawing algorithm. ''Note: The C++ API supports hooking <tt>IBaseMenu</tt> drawing procedures and adding raw text; this will be added to the scripting API soon.''<br />
<br />
Internally, Menus are drawn via a ''RenderMenu'' algorithm. This algorithm creates a temporary panel and fills it with items from menus. This panel is then displayed to a client. The algorithm attempts to create a consistent feel across all menus, and across all styles. Thus any menu displayed via the <tt>IBaseMenu</tt> class, or <tt>Menu</tt> Handles, will look and act the same, and the Menu API is based off the Panel API.<br />
<br />
<br />
=Callbacks=<br />
==Overview==<br />
Menus are a callback based system. Each callback represents an action that occurs during a ''menu display cycle''. A cycle consists of a number of notifications:<br />
*Start notification.<br />
**Display notification if the menu can be displayed to the client.<br />
**Either an item select or menu cancel notification.<br />
*End notification.<br />
<br />
Since ''End'' signifies the end of a full display cycle, it is usually used to destroy temporary menus.<br />
<br />
==Specification==<br />
A detailed explanation of these events is below. For C++, an <tt>IBaseMenu</tt> pointer is always available. For SourcePawn, a <tt>Menu</tt> Handle and a <tt>MenuAction</tt> are always set in the <tt>MenuHandler</tt> callback. Unlike C++, the SourcePawn API allows certain actions to only be called if they are requested at menu creation time. This is an optimization. However, certain actions cannot be prevented from being called.<br />
<br />
*'''Start'''. The menu has been acknowledged. This does not mean it will be displayed; however, it guarantees that "OnMenuEnd" will be called.<br />
**<tt>OnMenuStart()</tt> in C++.<br />
**<tt>MenuAction_Start</tt> in SourcePawn. This action is not triggered unless requested.<br />
***<tt>param1</tt>: Ignored (always 0).<br />
***<tt>param2</tt>: Ignored (always 0).<br />
*'''Display'''. The menu is being displayed to a client.<br />
**<tt>OnMenuDisplay()</tt> in C++. An <tt>IMenuPanel</tt> pointer and client index are available.<br />
**<tt>MenuAction_Display</tt> in SourcePawn. This action is not triggered unless requested.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A Handle to a menu panel.<br />
*'''Select'''. An item on the menu has been selected. The item position given will be the position in the menu, rather than the key pressed (unless the menu is a raw panel). <br />
**<tt>OnMenuSelect()</tt> in C++. A client index and item position are passed.<br />
**<tt>MenuAction_Select</tt> in SourcePawn. This action is always triggerable, whether requested or not.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: An item position.<br />
*'''Cancel'''. The menu's display to one client has been cancelled.<br />
**<tt>OnMenuCancel()</tt> in C++. A reason for cancellation is provided.<br />
**<tt>MenuAction_Cancel</tt> in SourcePawn. This action is always triggerable, whether requested or not.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A menu cancellation reason code.<br />
*'''End'''. The menu's display cycle has finished; this means that the "Start" action has occurred, and either "Select" or "Cancel" has occurred thereafter. This is typically where menu resources are removed/deleted.<br />
**<tt>OnMenuEnd()</tt> in C++.<br />
**<tt>MenuAction_End</tt> in SourcePawn. This action is always triggered, whether requested or not.<br />
***<tt>param1</tt>: A menu end reason code.<br />
***<tt>param2</tt>: If param1 was MenuEnd_Cancelled, this contains a menu cancellation reason code.<br />
<br />
==Panels==<br />
For panels, the callback rules change. Panels only receive two of the above callbacks, and it is guaranteed that only one of them will be called for a given display cycle. For C++, the <tt>IBaseMenu</tt> pointer will always be <tt>NULL</tt>. For SourcePawn, the menu Handle will always be <tt>INVALID_HANDLE</tt>.<br />
<br />
*'''Select'''. A key has been pressed. This can be any number and should not be considered as reliably in bounds. For example, even if you only had 2 items in your panel, a client could trigger a key press of "43."<br />
**<tt>OnMenuSelect()</tt> in C++. A client index and key number pressed are passed.<br />
**<tt>MenuAction_Select</tt> in SourcePawn.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: Number of the key pressed.<br />
*'''Cancel'''. The menu's display to one client has been cancelled.<br />
**<tt>OnMenuCancel()</tt> in C++. A reason for cancellation is provided.<br />
**<tt>MenuAction_Cancel</tt> in SourcePawn.<br />
***<tt>param1</tt>: A client index.<br />
***<tt>param2</tt>: A menu cancellation reason code.<br />
<br />
<br />
=Examples=<br />
First, let's start off with a very basic menu. We want the menu to look like this:<br />
<br />
<pre>Do you like apples?<br />
1. Yes<br />
2. No</pre><br />
<br />
We'll draw this menu with both a basic Menu and a Panel to show the API differences.<br />
<br />
==Basic Menu==<br />
First, let's write our example using the Menu building API.<br />
<br />
<pawn>public OnPluginStart()<br />
{<br />
RegConsoleCmd("menu_test1", Menu_Test1)<br />
}<br />
<br />
public MenuHandler1(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
/* If an option was selected, tell the client about the item. */<br />
if (action == MenuAction_Select)<br />
{<br />
new String:info[32]<br />
new bool:found = GetMenuItem(menu, param2, info, sizeof(info))<br />
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info)<br />
}<br />
/* If the menu was cancelled, print a message to the server about it. */<br />
else if (action == MenuAction_Cancel)<br />
{<br />
PrintToServer("Client %d's menu was cancelled. Reason: %d", param1, param2)<br />
}<br />
/* If the menu has ended, destroy it */<br />
else if (action == MenuAction_End)<br />
{<br />
CloseHandle(menu)<br />
}<br />
}<br />
<br />
public Action:Menu_Test1(client, args)<br />
{<br />
new Handle:menu = CreateMenu(MenuHandler1)<br />
SetMenuTitle(menu, "Do you like apples?")<br />
AddMenuItem(menu, "yes", "Yes")<br />
AddMenuItem(menu, "no", "No")<br />
SetMenuExitButton(menu, false)<br />
DisplayMenu(menu, client, 20)<br />
<br />
return Plugin_Handled<br />
}</pawn><br />
<br />
Note a few very important points from this example:<br />
*One of either <tt>Select</tt> or <tt>Cancel</tt> will always be sent to the action handler.<br />
*<tt>End</tt> will always be sent to the action handler.<br />
*We destroy our Menu in the <tt>End</tt> action, because our Handle is no longer needed. If we had destroyed the Menu after <tt>DisplayMenu</tt>, it would have canceled the menu's display to the client.<br />
*Menus, by default, have an exit button. We disabled this in our example.<br />
*Our menu is set to display for 20 seconds. That means that if the client does not select an item within 20 seconds, the menu will be canceled. This is usually desired for menus that are for voting. Note that unlike AMX Mod X, you do not need to set a timer to make sure the menu will be ended.<br />
*Although we created and destroyed a new Menu Handle, we didn't need to. It is perfectly acceptable to create the Handle once for the lifetime of the plugin.<br />
<br />
Our finished menu and attached console output looks like this (I selected "Yes"):<br />
<br />
[[Image:Basic_menu_1.PNG]]<br />
<br />
==Basic Panel==<br />
Now, let's rewrite our example to use Panels instead.<br />
<br />
<pawn>public OnPluginStart()<br />
{<br />
RegConsoleCmd("panel_test1", Panel_Test1)<br />
}<br />
<br />
public PanelHandler1(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
if (action == MenuAction_Select)<br />
{<br />
PrintToConsole(param1, "You selected item: %d", param2)<br />
} else if (action == MenuAction_Cancel) {<br />
PrintToServer("Client %d's menu was cancelled. Reason: %d", param1, param2)<br />
}<br />
}<br />
<br />
public Action:Panel_Test1(client, args)<br />
{<br />
new Handle:panel = CreatePanel();<br />
SetPanelTitle(panel, "Do you like apples?")<br />
DrawPanelItem(panel, "Yes")<br />
DrawPanelItem(panel, "No")<br />
<br />
SendPanelToClient(panel, client, PanelHandler1, 20)<br />
<br />
CloseHandle(panel)<br />
<br />
return Plugin_Handled<br />
}</pawn><br />
<br />
As you can see, Panels are significantly different.<br />
*We can destroy the Panel as soon as we're done displaying it. We can create the Panel once and keep re-using it, but we can destroy it at any time without interrupting client menus.<br />
*The Handler function gets much less data. Since panels are designed as a raw display, no "item" information is saved internally. Thus, the handler function only knows whether the display was canceled or whether (and what) numerical key was pressed.<br />
*There is no automation. You cannot add more than a certain amount of selectable items to a Panel and get pagination. Automated control functionality requires using the heftier Menu object API.<br />
<br />
Our finished display and console output looks like this (I selected "Yes"):<br />
<br />
[[Image:Basic_panel_1.PNG]]<br />
<br />
==Basic Paginated Menu==<br />
Now, let's take a more advanced example -- pagination. Let's say we want to build a menu for changing the map. An easy way to do this is to read the <tt>maplist.txt</tt> file at the start of a plugin and build a menu out of it.<br />
<br />
Since reading and parsing a file is an expensive operation, we only want to do this once per map. Thus we'll build the menu in <tt>OnMapStart</tt>, and we won't call <tt>CloseHandle</tt> until <tt>OnMapEnd</tt>.<br />
<br />
Source code:<br />
<pawn>new Handle:g_MapMenu = INVALID_HANDLE<br />
<br />
public OnPluginStart()<br />
{<br />
RegConsoleCmd("menu_changemap", Command_ChangeMap)<br />
}<br />
<br />
public OnMapStart()<br />
{<br />
g_MapMenu = BuildMapMenu()<br />
}<br />
<br />
public OnMapEnd()<br />
{<br />
if (g_MapMenu != INVALID_HANDLE)<br />
{<br />
CloseHandle(g_MapMenu)<br />
g_MapMenu = INVALID_HANDLE<br />
}<br />
}<br />
<br />
Handle:BuildMapMenu()<br />
{<br />
/* Open the file */<br />
new Handle:file = OpenFile("maplist.txt", "rt")<br />
if (file == INVALID_HANDLE)<br />
{<br />
return INVALID_HANDLE<br />
}<br />
<br />
/* Create the menu Handle */<br />
new Handle:menu = CreateMenu(Menu_ChangeMap);<br />
new String:mapname[255]<br />
while (!IsEndOfFile(file) && ReadFileLine(file, mapname, sizeof(mapname)))<br />
{<br />
if (mapname[0] == ';' || !IsCharAlpha(mapname[0]))<br />
{<br />
continue<br />
}<br />
/* Cut off the name at any whitespace */<br />
new len = strlen(mapname)<br />
for (new i=0; i<len; i++)<br />
{<br />
if (IsCharSpace(mapname[i]))<br />
{<br />
mapname[i] = '\0'<br />
break<br />
}<br />
}<br />
/* Check if the map is valid */<br />
if (!IsMapValid(mapname))<br />
{<br />
continue<br />
}<br />
/* Add it to the menu */<br />
AddMenuItem(menu, mapname, mapname)<br />
}<br />
/* Make sure we close the file! */<br />
CloseHandle(file)<br />
<br />
/* Finally, set the title */<br />
SetMenuTitle(menu, "Please select a map:")<br />
<br />
return menu<br />
}<br />
<br />
public Menu_ChangeMap(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
if (action == MenuAction_Select)<br />
{<br />
new String:info[32]<br />
<br />
/* Get item info */<br />
new bool:found = GetMenuItem(menu, param2, info, sizeof(info))<br />
<br />
/* Tell the client */<br />
PrintToConsole(param1, "You selected item: %d (found? %d info: %s)", param2, found, info)<br />
<br />
/* Change the map */<br />
ServerCommand("changelevel %s", info)<br />
}<br />
}<br />
<br />
public Action:Command_ChangeMap(client, args)<br />
{<br />
if (g_MapMenu == INVALID_HANDLE)<br />
{<br />
PrintToConsole(client, "The maplist.txt file was not found!")<br />
return Plugin_Handled<br />
} <br />
<br />
DisplayMenu(g_MapMenu, client, MENU_TIME_FOREVER)<br />
<br />
return Plugin_Handled<br />
}</pawn><br />
<br />
This menu results in many selections (my <tt>maplist.txt</tt> file had around 18 maps). So, our final menu has 3 pages, which side by side, look like:<br />
<br />
[[Image:Basic_menu_2_page1.PNG]]<br />
[[Image:Basic_menu_2_page2.PNG]]<br />
[[Image:Basic_menu_2_page3.PNG]]<br />
<br />
Finally, the console output printed this before the map changed to my selection, <tt>cs_office</tt>:<br />
<pre>You selected item: 8 (found? 1 info: cs_office)</pre><br />
<br />
Displaying and designing this Menu with a raw <tt>ShowMenu</tt> message or <tt>Panel</tt> API would be very time consuming and difficult. We would have to keep track of all the items in an array of hardcoded size, pages which the user is viewing, and write a function which calculated item selection based on current page and key press. The Menu system, thankfully, handles all of this for you.<br />
<br />
'''Notes:'''<br />
*Control options which are not available are not drawn. For example, in the first page, you cannot go "back," and in the last page, you cannot go "next." Despite this, the menu API tries to keep each the interface as consistent as possible. Thus, visually, each navigational control is always in the same position. <br />
*Although we specified no time out for our menu, if we had placed a timeout, flipping through pages does not affect the overall time. For example, if we had a timeout of 20, each successive page flip would continue to detract from the overall display time, rather than restart the allowed hold time back to 20.<br />
*If we had disabled the Exit button, options 8 and 9 would still be "Back" and "Next," respectively.<br />
*Again, we did not free the Menu Handle in <tt>MenuAction_End</tt>. This is because our menu is global/static, and we don't want to rebuild it every time.<br />
*These images show "Back." In SourceMod revisions 1011 and higher, "Back" is changed to "Previous," and "Back" is reserved for the special "ExitBack" functionality.<br />
<br />
<br />
=Voting=<br />
SourceMod also has API for displaying menus as votable choices to more than one client. SourceMod automatically handles selecting an item and randomly picking a tie-breaker. The voting API adds two new <tt>MenuAction</tt> values, which for vote displays, are '''always''' passed:<br />
<br />
*<tt>MenuAction_VoteStart</tt>: Fired after <tt>MenuAction_Start</tt> when the voting has officially started.<br />
*<tt>MenuAction_VoteEnd</tt>: Fired when all clients have either voted or cancelled their vote menu. The chosen item is passed through <tt>param1</tt>. This is fired '''before''' <tt>MenuAction_End</tt>. It is important to note that it does not supercede <tt>MenuAction_End</tt>, nor is it the same thing. Menus should never be destroyed in <tt>MenuAction_VoteEnd</tt>. '''Note:''' This is not called if <tt>SetVoteResultCallback</tt>() is used.<br />
*<tt>MenuAction_VoteCancel</tt>: Fired if the menu is cancelled while the vote is in progress. If this is called, <tt>MenuAction_VoteEnd</tt> or the result callback will not be called, but <tt>MenuAction_End</tt> will be afterwards. A vote cancellation reason is passed in <tt>param1</tt>. <br />
<br />
The voting system extends overall menus with two additional properties:<br />
*Only one vote can be active at a time. You must call <tt>IsVoteInProgress</tt>() or else <tt>VoteMenu</tt>() will fail.<br />
*If a client votes and then disconnects while the vote is still active, the client's vote will be invalidated.<br />
<br />
The example below shows has to create a function called <tt>DoVoteMenu()</tt> which will ask all clients whether or not they would like to change to the given map.<br />
<br />
==Simple Vote==<br />
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
CloseHandle(menu);<br />
} else if (action == MenuAction_VoteEnd) {<br />
/* 0=yes, 1=no */<br />
if (param1 == 0)<br />
{<br />
new String:map[64]<br />
GetMenuItem(menu, param1, map, sizeof(map))<br />
ServerCommand("changelevel %s", map);<br />
}<br />
}<br />
}<br />
<br />
DoVoteMenu(const String:map[])<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
new Handle:menu = CreateMenu(Handle_VoteMenu)<br />
SetMenuTitle(menu, "Change map to: %s?", map)<br />
AddMenuItem(menu, map, "Yes")<br />
AddMenuItem(menu, "no", "No")<br />
SetMenuExitButton(menu, false)<br />
VoteMenuToAll(menu, 20);<br />
}</pawn><br />
<br />
==Advanced Voting==<br />
If you need more information about voting results than <tt>MenuAction_VoteEnd</tt> gives you, you can choose to have a different callback invoked. The new callback will provide much more information, but at a price: <tt>MenuAction_VoteEnd</tt> will not be called, and you will have to decide how to interpret the results. This is done via <tt>SetVoteResultCallback</tt>().<br />
<br />
Example:<br />
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
CloseHandle(menu);<br />
}<br />
}<br />
<br />
public Handle_VoteResults(Handle:menu, <br />
num_votes, <br />
num_clients, <br />
const client_info[][2], <br />
num_items, <br />
const item_info[][2])<br />
{<br />
/* See if there were multiple winners */<br />
new winner = 0;<br />
if (num_items > 1<br />
&& (item_info[0][VOTEINFO_ITEM_VOTES] == item_info[1][VOTEINFO_ITEM_VOTES]))<br />
{<br />
winner = GetRandomInt(0, 1);<br />
}<br />
<br />
new String:map[64]<br />
GetMenuItem(menu, item_info[winner][VOTEINFO_ITEM_INDEX], map, sizeof(map))<br />
ServerCommand("changelevel %s", map)<br />
}<br />
<br />
DoVoteMenu(const String:map[])<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
new Handle:menu = CreateMenu(Handle_VoteMenu)<br />
SetVoteResultCallback(menu, Handle_VoteResults)<br />
SetMenuTitle(menu, "Change map to: %s?", map)<br />
AddMenuItem(menu, map, "Yes")<br />
AddMenuItem(menu, "no", "No")<br />
SetMenuExitButton(menu, false)<br />
VoteMenuToAll(menu, 20);<br />
}</pawn><br />
<br />
=ExitBack=<br />
ExitBack is a special term to refer to the "ExitBack Button." This button is disabled by default. Normally, paginated menus have no "Previous" item for the first page. If the "ExitBack" button is enabled, the "Previous" item will show up as "Back." <br />
<br />
Selecting the "ExitBack" option will exit the menu with <tt>MenuCancel_ExitBack</tt> and <tt>MenuEnd_ExitBack</tt>. The functionality of this is the same as a normal menu exit internally; extra functionality must be defined through the callbacks.<br />
<br />
=Translations=<br />
It is possible to dynamically translate menus to each player through the <tt>MenuAction_DisplayItem</tt> callback. A special native, <tt>RedrawMenuItem</tt>, is used to transform the text while inside the callback. Let's redo the vote example from earlier to be translated:<br />
<br />
<pawn>public Handle_VoteMenu(Handle:menu, MenuAction:action, param1, param2)<br />
{<br />
if (action == MenuAction_End)<br />
{<br />
/* This is called after VoteEnd */<br />
CloseHandle(menu);<br />
} else if (action == MenuAction_VoteEnd) {<br />
/* 0=yes, 1=no */<br />
if (param1 == 0)<br />
{<br />
new String:map[64]<br />
GetMenuItem(menu, param1, map, sizeof(map))<br />
ServerCommand("changelevel %s", map);<br />
}<br />
} else if (action == MenuAction_DisplayItem) {<br />
/* Get the display string, we'll use it as a translation phrase */<br />
decl String:display[64];<br />
GetMenuItem(menu, param2, "", 0, _, display, sizeof(display));<br />
<br />
/* Translate the string to the client's language */<br />
decl String:buffer[255];<br />
Format(buffer, sizeof(buffer), "%T", display, param1);<br />
<br />
/* Override the text */<br />
return RedrawMenuItem(buffer);<br />
} else if (action == MenuAction_Display) {<br />
/* Panel Handle is the second parameter */<br />
new Handle:panel = Handle:param2;<br />
<br />
/* Get the map name we're changing to from the first item */<br />
decl String:map[64];<br />
GetMenuItem(menu, 0, map, sizeof(map));<br />
<br />
/* Translate to our phrase */<br />
decl String:buffer[255];<br />
Format(buffer, sizeof(buffer), "%T", "Change map to?", client, map);<br />
<br />
SetPanelTitle(panel, buffer);<br />
}<br />
}<br />
<br />
DoVoteMenu(const String:map[])<br />
{<br />
if (IsVoteInProgress())<br />
{<br />
return;<br />
}<br />
<br />
new Handle:menu = CreateMenu(Handle_VoteMenu, MenuAction_DisplayItem|MenuAction_Display)<br />
SetMenuTitle(menu, "Change map to: %s?", map)<br />
AddMenuItem(menu, map, "Yes")<br />
AddMenuItem(menu, "no", "No")<br />
SetMenuExitButton(menu, false)<br />
VoteMenuToAll(menu, 20);<br />
}</pawn><br />
<br />
<br />
[[Category:SourceMod Development]]<br />
[[Category:SourceMod Scripting]]</div>Fredd