Difference between revisions of "Menus Step By Step (SourceMod Scripting)"
(Finally finished (I hope)) |
m (spelling mistake) |
||
(22 intermediate revisions by 9 users not shown) | |||
Line 9: | Line 9: | ||
Usage of the [[Translations (SourceMod Scripting)|Translations]] system is highly recommended and will be used in this example. | Usage of the [[Translations (SourceMod Scripting)|Translations]] system is highly recommended and will be used in this example. | ||
− | < | + | <sourcepawn>#define CHOICE1 "#choice1" |
− | |||
− | #define CHOICE1 "#choice1" | ||
#define CHOICE2 "#choice2" | #define CHOICE2 "#choice2" | ||
#define CHOICE3 "#choice3" | #define CHOICE3 "#choice3" | ||
− | public OnPluginStart() | + | public void OnPluginStart() |
{ | { | ||
− | + | LoadTranslations("menu_test.phrases"); | |
− | + | RegConsoleCmd("menu_test1", Menu_Test1); | |
} | } | ||
− | public MenuHandler1( | + | public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2) |
{ | { | ||
− | + | switch(action) | |
− | + | { | |
− | + | case MenuAction_Start: | |
− | + | { | |
− | + | PrintToServer("Displaying menu"); | |
− | + | } | |
− | + | ||
− | + | case MenuAction_Display: | |
− | + | { | |
− | + | char buffer[255]; | |
− | + | Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); | |
− | + | ||
− | + | Panel panel = view_as<Panel>(param2); | |
− | + | panel.SetTitle(buffer); | |
− | + | PrintToServer("Client %d was sent menu with panel %x", param1, param2); | |
− | + | } | |
− | + | ||
− | + | case MenuAction_Select: | |
− | + | { | |
− | + | char info[32]; | |
− | + | menu.GetItem(param2, info, sizeof(info)); | |
− | + | if (StrEqual(info, CHOICE3)) | |
− | + | { | |
− | + | PrintToServer("Client %d somehow selected %s despite it being disabled", param1, info); | |
− | + | } | |
− | + | else | |
− | + | { | |
− | + | PrintToServer("Client %d selected %s", param1, info); | |
− | + | } | |
− | + | } | |
− | + | ||
− | + | case MenuAction_Cancel: | |
− | + | { | |
− | + | PrintToServer("Client %d's menu was cancelled for reason %d", param1, param2); | |
− | + | } | |
− | + | ||
− | + | case MenuAction_End: | |
− | + | { | |
− | + | delete menu; | |
− | + | } | |
− | + | ||
− | + | case MenuAction_DrawItem: | |
− | + | { | |
− | + | int style; | |
− | + | char info[32]; | |
− | + | menu.GetItem(param2, info, sizeof(info), style); | |
− | + | ||
− | + | if (StrEqual(info, CHOICE3)) | |
− | + | { | |
− | + | return ITEMDRAW_DISABLED; | |
− | + | } | |
− | + | else | |
− | + | { | |
+ | return style; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | case MenuAction_DisplayItem: | ||
+ | { | ||
+ | char info[32]; | ||
+ | menu.GetItem(param2, info, sizeof(info)); | ||
+ | |||
+ | char display[64]; | ||
+ | |||
+ | if (StrEqual(info, CHOICE3)) | ||
+ | { | ||
+ | Format(display, sizeof(display), "%T", "Choice 3", param1); | ||
+ | return RedrawMenuItem(display); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return 0; | ||
} | } | ||
− | public Action | + | public Action Menu_Test1(int client, int args) |
{ | { | ||
− | + | Menu menu = new Menu(MenuHandler1, MENU_ACTIONS_ALL); | |
− | + | menu.SetTitle("%T", "Menu Title", LANG_SERVER); | |
− | + | menu.AddItem(CHOICE1, "Choice 1"); | |
− | + | menu.AddItem(CHOICE2, "Choice 2"); | |
− | + | menu.AddItem(CHOICE3, "Choice 3"); | |
− | + | menu.ExitButton = false; | |
− | + | menu.Display(client, 20); | |
− | + | ||
− | + | return Plugin_Handled; | |
− | }</ | + | }</sourcepawn> |
+ | |||
+ | The translation file that goes with it... addons/sourcemod/translations/menu_test.phrases.txt | ||
+ | |||
+ | <pre>"Phrases" | ||
+ | { | ||
+ | "Choice 3" | ||
+ | { | ||
+ | "en" "Choice Translated" | ||
+ | } | ||
+ | |||
+ | "Menu Title" | ||
+ | { | ||
+ | "en" "Menu Title Translated" | ||
+ | } | ||
+ | }</pre> | ||
== Step by Step == | == Step by Step == | ||
Line 95: | Line 128: | ||
=== OnPluginStart === | === OnPluginStart === | ||
− | < | + | <sourcepawn>public void OnPluginStart() |
{ | { | ||
− | + | LoadTranslations("menu_test.phrases"); | |
− | + | RegConsoleCmd("menu_test1", Menu_Test1); | |
− | }</ | + | }</sourcepawn> |
This block is here to register the command /menu_test1 so that this menu can be tested. | This block is here to register the command /menu_test1 so that this menu can be tested. | ||
Line 109: | Line 142: | ||
Menu_Test1 is a ConCmd, which is beyond the scope of this page. See: [[Commands (SourceMod Scripting)|Commands]] for more details on that. We're going to be talking about what's in it. | Menu_Test1 is a ConCmd, which is beyond the scope of this page. See: [[Commands (SourceMod Scripting)|Commands]] for more details on that. We're going to be talking about what's in it. | ||
− | ==== | + | ==== Menu ==== |
+ | |||
+ | <sourcepawn> Menu menu = new Menu(MenuHandler1, MENU_ACTIONS_ALL);</sourcepawn> | ||
− | + | Menu takes two arguments. | |
− | + | The first is a function that matches the [https://sm.alliedmods.net/new-api/menus/MenuHandler MenuHandler] callback signature. | |
− | The | + | The second is a bitmask of which MenuAction events we are going to handle in our MenuHandler. There are 7 that apply to standard menus: |
+ | * MenuAction_Start - Called once when a menu is displayed | ||
+ | * MenuAction_Display - Called once for each client a menu is displayed to | ||
+ | * MenuAction_Select - Called when a client makes a selection from the menu that isn't Previous, Next, Back, or Exit. | ||
+ | * MenuAction_Cancel - Called when a client closes a menu or it is closed on them | ||
+ | * MenuAction_End - Called once when all clients have closed the menu | ||
+ | * MenuAction_DrawItem - Called once for each item for each user a menu is displayed to. Can change the menu item style. | ||
+ | * MenuAction_DisplayItem. - Called once for each item for each user a menu is displayed to. Can change the menu item text. | ||
− | + | These will be discussed in more detail in the MenuHandler1 section. | |
Of those 7, 3 are called whether you specify them or not: MenuAction_Select, MenuAction_Cancel, and MenuAction_End. | Of those 7, 3 are called whether you specify them or not: MenuAction_Select, MenuAction_Cancel, and MenuAction_End. | ||
Line 125: | Line 167: | ||
To specify just a few of them, you can do this: | To specify just a few of them, you can do this: | ||
− | < | + | <sourcepawn> Menu menu = new Menu(MenuHandler1, MenuAction_Start|MenuAction_Select|MenuAction_Cancel|MenuAction_End);</sourcepawn> |
− | ==== | + | ==== menu.SetTitle ==== |
− | < | + | <sourcepawn> menu.SetTitle("%T", "Menu Title", LANG_SERVER);</sourcepawn> |
Sets the menu's title. This example uses the translation system and the server's default language. This isn't strictly necessary as we will set the menu title again later. | Sets the menu's title. This example uses the translation system and the server's default language. This isn't strictly necessary as we will set the menu title again later. | ||
− | ==== | + | ==== menu.AddItem ==== |
− | < | + | <sourcepawn> menu.AddItem(CHOICE1, "Choice 1"); |
− | + | menu.AddItem(CHOICE2, "Choice 2"); | |
− | + | menu.AddItem(CHOICE3, "Choice 3");</sourcepawn> | |
A menu isn't useful without something in it! | A menu isn't useful without something in it! | ||
− | + | menu.AddItem has 2 required arguments and one optional argument. | |
− | The | + | The 2 required arguments are: |
− | The | + | The Info string, and the Display string. |
The Info String is a string that is used to uniquely identify this menu item. It is never displayed to the user, but is used in various callbacks. | The Info String is a string that is used to uniquely identify this menu item. It is never displayed to the user, but is used in various callbacks. | ||
Line 150: | Line 192: | ||
The optional argument is the menu's item draw style. It can be one of these values: | The optional argument is the menu's item draw style. It can be one of these values: | ||
− | ITEMDRAW_DEFAULT - Displays text as normal. | + | * ITEMDRAW_DEFAULT - Displays text as normal. |
− | ITEMDRAW_DISABLED - Item is displayed and has a number, but can't be selected. | + | * ITEMDRAW_DISABLED - Item is displayed and has a number, but can't be selected. |
− | ITEMDRAW_RAWLINE - Item is displayed, but doesn't have a number assigned to it and thus can't be selected | + | * ITEMDRAW_RAWLINE - Item is displayed, but doesn't have a number assigned to it and thus can't be selected |
− | ITEMDRAW_NOTEXT - Draws an item with no text. Not very useful. | + | * ITEMDRAW_NOTEXT - Draws an item with no text. Not very useful. |
− | ITEMDRAW_SPACER - Item is blank, but has a number. Can't be selected. | + | * ITEMDRAW_SPACER - Item is blank, but has a number. Can't be selected. |
− | ITEMDRAW_IGNORE - Item is blank with no number. Can't be selected. | + | * ITEMDRAW_IGNORE - Item is blank with no number. Can't be selected. |
− | ITEMDRAW_CONTROL - Don't use this one. It's used for the items SourceMod automatically adds. | + | * ITEMDRAW_CONTROL - Don't use this one. It's used for the items SourceMod automatically adds. |
Be aware that if your CreateMenu second argument includes MenuAction_DrawItem or MENU_ACTIONS_ALL and you don't actually implement the MenuAction_DrawItem callback (or implement it and don't return the current item's style if you don't change it), your style here will be completely ignored. | Be aware that if your CreateMenu second argument includes MenuAction_DrawItem or MENU_ACTIONS_ALL and you don't actually implement the MenuAction_DrawItem callback (or implement it and don't return the current item's style if you don't change it), your style here will be completely ignored. | ||
− | ==== | + | ==== menu.ExitButton ==== |
− | < | + | <sourcepawn> menu.ExitButton = false;</sourcepawn> |
Defaults to true. | Defaults to true. | ||
Line 167: | Line 209: | ||
It set to false, the menu won't have an exit button. | It set to false, the menu won't have an exit button. | ||
− | ==== | + | ==== menu.ExitBackButton ==== |
− | < | + | <sourcepawn> menu.ExitBackButton = false;</sourcepawn> |
(This isn't in the code above, but it should be mentioned) | (This isn't in the code above, but it should be mentioned) | ||
Line 180: | Line 222: | ||
This can be used to dynamically set if a menu is being called as its own menu or as a submenu. | This can be used to dynamically set if a menu is being called as its own menu or as a submenu. | ||
− | ==== | + | ==== menu.Display ==== |
− | < | + | <sourcepawn> menu.Display(client, 20);</sourcepawn> |
− | DisplayMenu takes | + | DisplayMenu takes 2 arguments: |
A Menu handle, a client index, and the amount of time in seconds to show the menu. | A Menu handle, a client index, and the amount of time in seconds to show the menu. | ||
− | If you want the menu to show forever, pass MENU_TIME_FOREVER as the | + | If you want the menu to show forever, pass MENU_TIME_FOREVER as the second argument. |
=== MenuHandler1 === | === MenuHandler1 === | ||
− | < | + | <sourcepawn>public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2)</sourcepawn> |
MenuHandler1 is a function whose signature matches the MenuHandler callback. It can be named something other than MenuHandler1. It must always be public and it will always have these arguments: Handle, MenuAction, cell, and cell in that order. | MenuHandler1 is a function whose signature matches the MenuHandler callback. It can be named something other than MenuHandler1. It must always be public and it will always have these arguments: Handle, MenuAction, cell, and cell in that order. | ||
Line 196: | Line 238: | ||
The Handle argument is always the menu that called the handler. | The Handle argument is always the menu that called the handler. | ||
− | The | + | The MenuAction argument is always one of 7 actions for a standard menu. They are: MenuAction_Start, MenuAction_Display, MenuAction_Select, MenuAction_Cancel, MenuAction_End, MenuAction_DrawItem, and MenuAction_DisplayItem. We will discuss each of these as we reach their code. |
− | The MenuAction argument is | + | The two cell arguments meaning depend on the MenuAction argument. '''A common mistake is to assume param1 is the client.''' This is incorrect for MenuAction_Start, MenuAction_End, MenuAction_VoteEnd, MenuAction_VoteStart, and MenuAction_VoteCancel. |
==== MenuAction_Start ==== | ==== MenuAction_Start ==== | ||
− | < | + | <sourcepawn> case MenuAction_Start: |
− | + | { | |
− | + | PrintToServer("Displaying menu"); | |
− | + | }</sourcepawn> | |
+ | |||
+ | * param1: not set | ||
+ | * param2: not set | ||
+ | * return: 0 (or don't return) | ||
MenuAction_Start doesn't set param1 and param2. It is fired when the menu is displayed to one or more users using DisplayMenu, DisplayMenuAtItem, VoteMenu, or VoteMenuToAll. | MenuAction_Start doesn't set param1 and param2. It is fired when the menu is displayed to one or more users using DisplayMenu, DisplayMenuAtItem, VoteMenu, or VoteMenuToAll. | ||
Line 211: | Line 257: | ||
==== MenuAction_Display ==== | ==== MenuAction_Display ==== | ||
− | < | + | <sourcepawn> case MenuAction_Display: |
− | + | { | |
− | + | char buffer[255]; | |
− | + | Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); | |
− | |||
− | + | Panel panel = view_as<Panel>(param2); | |
+ | panel.SetTitle(buffer); | ||
+ | PrintToServer("Client %d was sent menu with panel %x", param1, param2); | ||
+ | }</sourcepawn> | ||
− | + | * param1: client index | |
+ | * param2: MenuPanel Handle | ||
+ | * return: 0 (or don't return) | ||
+ | |||
+ | MenuAction_Display is called once for each user a menu is displayed to. param1 is the client, param2 is the MenuPanel handle. | ||
+ | |||
+ | SetPanelTitle is used to change the menu's title based on the language of the user viewing it using the Translations system. | ||
==== MenuAction_Select ==== | ==== MenuAction_Select ==== | ||
− | < | + | <sourcepawn> case MenuAction_Select: |
− | + | { | |
− | + | char info[32]; | |
− | + | menu.GetItem(param2, info, sizeof(info)); | |
− | + | if (StrEqual(info, CHOICE3)) | |
− | + | { | |
− | + | PrintToServer("Client %d somehow selected %s despite it being disabled", param1, info); | |
− | + | } | |
− | + | else | |
− | + | { | |
− | + | PrintToServer("Client %d selected %s", param1, info); | |
− | + | } | |
− | + | }</sourcepawn> | |
+ | |||
+ | * param1: client index | ||
+ | * param2: item number for use with GetMenuItem | ||
+ | * return: 0 (or don't return) | ||
MenuAction_Select is called when a user selects a non-control item on the menu (something added using AddMenuItem). param1 is the client, param2 is the menu position of the item the client selected. | MenuAction_Select is called when a user selects a non-control item on the menu (something added using AddMenuItem). param1 is the client, param2 is the menu position of the item the client selected. | ||
Line 244: | Line 302: | ||
==== MenuAction_Cancel ==== | ==== MenuAction_Cancel ==== | ||
− | + | case MenuAction_Cancel: | |
− | + | { | |
− | + | PrintToServer("Client %d's menu was cancelled for reason %d", param1, param2); | |
− | + | } | |
+ | |||
+ | * param1: client index | ||
+ | * param2: MenuCancel reason | ||
+ | * return: 0 (or don't return) | ||
MenuAction_Cancel is called whenever a user closes a menu or it is closed for them for another reason. param1 is the client, param2 is the close reason. | MenuAction_Cancel is called whenever a user closes a menu or it is closed for them for another reason. param1 is the client, param2 is the close reason. | ||
Line 253: | Line 315: | ||
The close reasons you can receive are: | The close reasons you can receive are: | ||
− | MenuCancel_Disconnected - The client got disconnected from the server. | + | * MenuCancel_Disconnected - The client got disconnected from the server. |
− | MenuCancel_Interrupted - Another menu opened, automatically closing our menu. | + | * MenuCancel_Interrupted - Another menu opened, automatically closing our menu. |
− | MenuCancel_Exit - The client selected Exit. Not called if SetMenuExitBack was set to true. Not called if SetMenuExit was set to false. | + | * MenuCancel_Exit - The client selected Exit. Not called if SetMenuExitBack was set to true. Not called if SetMenuExit was set to false. |
− | MenuCancel_NoDisplay - Our menu never displayed to the client for whatever reason. | + | * MenuCancel_NoDisplay - Our menu never displayed to the client for whatever reason. |
− | MenuCancel_Timeout - The menu timed out. Not called if the menu time was MENU_TIME_FOREVER. | + | * MenuCancel_Timeout - The menu timed out. Not called if the menu time was MENU_TIME_FOREVER. |
− | MenuCancel_ExitBack - The client selected Back. Only called if SetMenuExitBack has been called and set to true before the menu was sent. Not called if SetMenuExit was set to false. | + | * MenuCancel_ExitBack - The client selected Back. Only called if SetMenuExitBack has been called and set to true before the menu was sent. Not called if SetMenuExit was set to false. |
==== MenuAction_End ==== | ==== MenuAction_End ==== | ||
− | < | + | <sourcepawn> case MenuAction_End: |
− | + | { | |
− | + | delete menu; | |
− | + | }</sourcepawn> | |
+ | |||
+ | * param1: MenuEnd reason | ||
+ | * param2: If param1 is MenuEnd_Cancelled, the MenuCancel reason | ||
+ | * return: 0 (or don't return) | ||
− | MenuAction_End is called when all clients have closed a menu. For menus that are not going to be redisplayed, it is required that you call CloseHandle on the menu here. | + | MenuAction_End is called when ''all'' clients have closed a menu or vote. For menus that are not going to be redisplayed, it is required that you call CloseHandle on the menu here. |
The parameters are rarely used in MenuAction_End. param1 is the menu end reason. param2 depends on param1. | The parameters are rarely used in MenuAction_End. param1 is the menu end reason. param2 depends on param1. | ||
Line 272: | Line 338: | ||
The end reasons you can receive for normal menus are: | The end reasons you can receive for normal menus are: | ||
− | MenuEnd_Selected - The menu closed because an item was selected (MenuAction_Select was fired) | + | * MenuEnd_Selected - The menu closed because an item was selected (MenuAction_Select was fired) |
− | MenuEnd_Cancelled - The menu was cancelled (MenuAction_Cancel was fired), cancel reason is in param2; cancel reason can be any of the ones listed in MenuAction_Cancel except MenuCancel_Exit or MenuCancel_ExitBack | + | * MenuEnd_Cancelled - The menu was cancelled (MenuAction_Cancel was fired), cancel reason is in param2; cancel reason can be any of the ones listed in MenuAction_Cancel except MenuCancel_Exit or MenuCancel_ExitBack |
− | MenuEnd_Exit - The menu was exited via the Exit item (MenuAction_Cancel was fired with param2 set to MenuCancel_Exit) | + | * MenuEnd_Exit - The menu was exited via the Exit item (MenuAction_Cancel was fired with param2 set to MenuCancel_Exit) |
− | MenuEnd_ExitBack - The menu was exited via the ExitBack item (MenuAction_Cancel was fired with param 2 set to MenuCancel_ExitBack) | + | * MenuEnd_ExitBack - The menu was exited via the ExitBack item (MenuAction_Cancel was fired with param 2 set to MenuCancel_ExitBack) |
− | Note: You do | + | Note: You do '''not''' have the client index during this callback, so it's far too late to do anything useful with this information. |
==== MenuAction_DrawItem ==== | ==== MenuAction_DrawItem ==== | ||
− | < | + | <sourcepawn> case MenuAction_DrawItem: |
− | + | { | |
− | + | int style; | |
− | + | char info[32]; | |
− | + | menu.GetItem(param2, info, sizeof(info), style); | |
− | + | ||
− | + | if (StrEqual(info, CHOICE3)) | |
− | + | { | |
− | + | return ITEMDRAW_DISABLED; | |
− | + | } | |
− | + | else | |
− | + | { | |
− | + | return style; | |
− | + | } | |
− | + | }</sourcepawn> | |
+ | |||
+ | * param1: client index | ||
+ | * param2: item number for use with GetMenuItem | ||
+ | * return: new ITEMDRAW properties or style from GetMenuItem. Since 0 is ITEMDRAW_DEFAULT, returning 0 clears all styles for this item. | ||
MenuAction_DrawItem is called once for each item on the menu for each user. You can manipulate its draw style here. param1 is the client, param2 is the menu position. | MenuAction_DrawItem is called once for each item on the menu for each user. You can manipulate its draw style here. param1 is the client, param2 is the menu position. | ||
Line 304: | Line 374: | ||
You should return the style you want the menu item to have. In our example, if client 1 is viewing the menu, we want CHOICE3 to be disabled. | You should return the style you want the menu item to have. In our example, if client 1 is viewing the menu, we want CHOICE3 to be disabled. | ||
− | + | the return value is a bitfield, so to apply multiple styles, you do something like this: | |
+ | |||
+ | return ITEMDRAW_NOTEXT | ITEMDRAW_SPACER; | ||
+ | |||
+ | '''Failing to return the current item's style if you don't change the style is a programmer error.''' | ||
==== MenuAction_DisplayItem ==== | ==== MenuAction_DisplayItem ==== | ||
− | < | + | <sourcepawn> case MenuAction_DisplayItem: |
− | + | { | |
− | + | char info[32]; | |
− | + | menu.GetItem(param2, info, sizeof(info)); | |
− | + | ||
− | + | char display[64]; | |
− | + | ||
− | + | if (StrEqual(info, CHOICE3)) | |
− | + | { | |
− | + | Format(display, sizeof(display), "%T", "Choice 3", param1); | |
− | + | return RedrawMenuItem(display); | |
− | + | } | |
− | + | }</sourcepawn> | |
+ | |||
+ | * param1: client index | ||
+ | * param2: item number for use with GetMenuItem | ||
+ | * return: return value from RedrawMenuItem or 0 for no change | ||
− | + | MenuAction_DisplayItem is called once for each item on the menu for each user. You can manipulate its text here. param1 is the client, param2 is the menu position. | |
This callback is intended for use with the Translation system. | This callback is intended for use with the Translation system. | ||
Line 335: | Line 413: | ||
==== return ==== | ==== return ==== | ||
− | < | + | <sourcepawn> return 0;</sourcepawn> |
− | + | If you handle MenuAction_DrawItem or MenuAction_DisplayItem, you will get the following warning if you fail to return 0 after the switch block: | |
<code>warning 209: function "MenuHandler1" should return a value</code> | <code>warning 209: function "MenuHandler1" should return a value</code> | ||
+ | This is because MenuAction_DrawItem and MenuAction_DisplayItem have return values, while the other actions only return 0. | ||
== The End == | == The End == | ||
Hopefully this walk through a simple menu helped you understand why each call is being made where. Menus have some options that we didn't explore here, such as disabling pagination (which is enabled by default). You may want to refer to the main Menu documentation for more details. | Hopefully this walk through a simple menu helped you understand why each call is being made where. Menus have some options that we didn't explore here, such as disabling pagination (which is enabled by default). You may want to refer to the main Menu documentation for more details. |
Latest revision as of 01:59, 26 August 2021
The SourceMod Menu API is fairly useful for displaying an information panel, a menu, or a vote to users. This document talks specifically about menus and how to create one.
Contents
This is the working code example that we'll be talking about in the upcoming sections. If you need to, you can come back and look at it as you read the rest of this page.
We will be using PrintToServer to print text to the server console as we run through the events.
Usage of the Translations system is highly recommended and will be used in this example.
#define CHOICE1 "#choice1" #define CHOICE2 "#choice2" #define CHOICE3 "#choice3" public void OnPluginStart() { LoadTranslations("menu_test.phrases"); RegConsoleCmd("menu_test1", Menu_Test1); } public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2) { switch(action) { case MenuAction_Start: { PrintToServer("Displaying menu"); } case MenuAction_Display: { char buffer[255]; Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); Panel panel = view_as<Panel>(param2); panel.SetTitle(buffer); PrintToServer("Client %d was sent menu with panel %x", param1, param2); } case MenuAction_Select: { char info[32]; menu.GetItem(param2, info, sizeof(info)); if (StrEqual(info, CHOICE3)) { PrintToServer("Client %d somehow selected %s despite it being disabled", param1, info); } else { PrintToServer("Client %d selected %s", param1, info); } } case MenuAction_Cancel: { PrintToServer("Client %d's menu was cancelled for reason %d", param1, param2); } case MenuAction_End: { delete menu; } case MenuAction_DrawItem: { int style; char info[32]; menu.GetItem(param2, info, sizeof(info), style); if (StrEqual(info, CHOICE3)) { return ITEMDRAW_DISABLED; } else { return style; } } case MenuAction_DisplayItem: { char info[32]; menu.GetItem(param2, info, sizeof(info)); char display[64]; if (StrEqual(info, CHOICE3)) { Format(display, sizeof(display), "%T", "Choice 3", param1); return RedrawMenuItem(display); } } } return 0; } public Action Menu_Test1(int client, int args) { Menu menu = new Menu(MenuHandler1, MENU_ACTIONS_ALL); menu.SetTitle("%T", "Menu Title", LANG_SERVER); menu.AddItem(CHOICE1, "Choice 1"); menu.AddItem(CHOICE2, "Choice 2"); menu.AddItem(CHOICE3, "Choice 3"); menu.ExitButton = false; menu.Display(client, 20); return Plugin_Handled; }
The translation file that goes with it... addons/sourcemod/translations/menu_test.phrases.txt
"Phrases" { "Choice 3" { "en" "Choice Translated" } "Menu Title" { "en" "Menu Title Translated" } }
Step by Step
OnPluginStart
public void OnPluginStart() { LoadTranslations("menu_test.phrases"); RegConsoleCmd("menu_test1", Menu_Test1); }
This block is here to register the command /menu_test1 so that this menu can be tested.
This block is beyond the scope of this page. See: Plugin Structure for OnPluginStart, Translations for LoadTranslations, and Commands for RegConsoleCmd.
Menu_Test1
Menu_Test1 is a ConCmd, which is beyond the scope of this page. See: Commands for more details on that. We're going to be talking about what's in it.
Menu
Menu menu = new Menu(MenuHandler1, MENU_ACTIONS_ALL);
Menu takes two arguments.
The first is a function that matches the MenuHandler callback signature.
The second is a bitmask of which MenuAction events we are going to handle in our MenuHandler. There are 7 that apply to standard menus:
- MenuAction_Start - Called once when a menu is displayed
- MenuAction_Display - Called once for each client a menu is displayed to
- MenuAction_Select - Called when a client makes a selection from the menu that isn't Previous, Next, Back, or Exit.
- MenuAction_Cancel - Called when a client closes a menu or it is closed on them
- MenuAction_End - Called once when all clients have closed the menu
- MenuAction_DrawItem - Called once for each item for each user a menu is displayed to. Can change the menu item style.
- MenuAction_DisplayItem. - Called once for each item for each user a menu is displayed to. Can change the menu item text.
These will be discussed in more detail in the MenuHandler1 section.
Of those 7, 3 are called whether you specify them or not: MenuAction_Select, MenuAction_Cancel, and MenuAction_End.
MENU_ACTIONS_DEFAULT sets just the 3 required fields, while MENU_ACTIONS_ALL specifies all 10 actions (including the 3 vote actions).
To specify just a few of them, you can do this:
Menu menu = new Menu(MenuHandler1, MenuAction_Start|MenuAction_Select|MenuAction_Cancel|MenuAction_End);
menu.SetTitle("%T", "Menu Title", LANG_SERVER);
Sets the menu's title. This example uses the translation system and the server's default language. This isn't strictly necessary as we will set the menu title again later.
menu.AddItem(CHOICE1, "Choice 1"); menu.AddItem(CHOICE2, "Choice 2"); menu.AddItem(CHOICE3, "Choice 3");
A menu isn't useful without something in it! menu.AddItem has 2 required arguments and one optional argument.
The 2 required arguments are:
The Info string, and the Display string.
The Info String is a string that is used to uniquely identify this menu item. It is never displayed to the user, but is used in various callbacks.
The Display String is the default text to be used for this item on the menu. It can be changed via the menu's MenuAction_DisplayItem callback.
The optional argument is the menu's item draw style. It can be one of these values:
- ITEMDRAW_DEFAULT - Displays text as normal.
- ITEMDRAW_DISABLED - Item is displayed and has a number, but can't be selected.
- ITEMDRAW_RAWLINE - Item is displayed, but doesn't have a number assigned to it and thus can't be selected
- ITEMDRAW_NOTEXT - Draws an item with no text. Not very useful.
- ITEMDRAW_SPACER - Item is blank, but has a number. Can't be selected.
- ITEMDRAW_IGNORE - Item is blank with no number. Can't be selected.
- ITEMDRAW_CONTROL - Don't use this one. It's used for the items SourceMod automatically adds.
Be aware that if your CreateMenu second argument includes MenuAction_DrawItem or MENU_ACTIONS_ALL and you don't actually implement the MenuAction_DrawItem callback (or implement it and don't return the current item's style if you don't change it), your style here will be completely ignored.
menu.ExitButton = false;
Defaults to true.
It set to false, the menu won't have an exit button.
menu.ExitBackButton = false;
(This isn't in the code above, but it should be mentioned)
Defaults to false.
Should only be used if the menu is a submenu, with the menu set up to check the cancel reason to see if the ExitBack action was used.
If set to true, replaces the Exit item with the Back item. The Back item still cancels the menu, but has a separate cancel reason.
This can be used to dynamically set if a menu is being called as its own menu or as a submenu.
menu.Display(client, 20);
DisplayMenu takes 2 arguments:
A Menu handle, a client index, and the amount of time in seconds to show the menu.
If you want the menu to show forever, pass MENU_TIME_FOREVER as the second argument.
MenuHandler1
public int MenuHandler1(Menu menu, MenuAction action, int param1, int param2)
MenuHandler1 is a function whose signature matches the MenuHandler callback. It can be named something other than MenuHandler1. It must always be public and it will always have these arguments: Handle, MenuAction, cell, and cell in that order.
The Handle argument is always the menu that called the handler.
The MenuAction argument is always one of 7 actions for a standard menu. They are: MenuAction_Start, MenuAction_Display, MenuAction_Select, MenuAction_Cancel, MenuAction_End, MenuAction_DrawItem, and MenuAction_DisplayItem. We will discuss each of these as we reach their code.
The two cell arguments meaning depend on the MenuAction argument. A common mistake is to assume param1 is the client. This is incorrect for MenuAction_Start, MenuAction_End, MenuAction_VoteEnd, MenuAction_VoteStart, and MenuAction_VoteCancel.
MenuAction_Start
case MenuAction_Start: { PrintToServer("Displaying menu"); }
- param1: not set
- param2: not set
- return: 0 (or don't return)
MenuAction_Start doesn't set param1 and param2. It is fired when the menu is displayed to one or more users using DisplayMenu, DisplayMenuAtItem, VoteMenu, or VoteMenuToAll.
MenuAction_Display
case MenuAction_Display: { char buffer[255]; Format(buffer, sizeof(buffer), "%T", "Vote Nextmap", param1); Panel panel = view_as<Panel>(param2); panel.SetTitle(buffer); PrintToServer("Client %d was sent menu with panel %x", param1, param2); }
- param1: client index
- param2: MenuPanel Handle
- return: 0 (or don't return)
MenuAction_Display is called once for each user a menu is displayed to. param1 is the client, param2 is the MenuPanel handle.
SetPanelTitle is used to change the menu's title based on the language of the user viewing it using the Translations system.
MenuAction_Select
case MenuAction_Select: { char info[32]; menu.GetItem(param2, info, sizeof(info)); if (StrEqual(info, CHOICE3)) { PrintToServer("Client %d somehow selected %s despite it being disabled", param1, info); } else { PrintToServer("Client %d selected %s", param1, info); } }
- param1: client index
- param2: item number for use with GetMenuItem
- return: 0 (or don't return)
MenuAction_Select is called when a user selects a non-control item on the menu (something added using AddMenuItem). param1 is the client, param2 is the menu position of the item the client selected.
Using the item position to check which item was selected is a bad idea, as item position is brittle and will break things if AddMenuItem or InsertMenuItem is used. It is recommended that you instead use the Menu item's info string, as done in the code above.
GetMenuItem is used here to fetch the info string.
MenuAction_Cancel
case MenuAction_Cancel: { PrintToServer("Client %d's menu was cancelled for reason %d", param1, param2); }
- param1: client index
- param2: MenuCancel reason
- return: 0 (or don't return)
MenuAction_Cancel is called whenever a user closes a menu or it is closed for them for another reason. param1 is the client, param2 is the close reason.
The close reasons you can receive are:
- MenuCancel_Disconnected - The client got disconnected from the server.
- MenuCancel_Interrupted - Another menu opened, automatically closing our menu.
- MenuCancel_Exit - The client selected Exit. Not called if SetMenuExitBack was set to true. Not called if SetMenuExit was set to false.
- MenuCancel_NoDisplay - Our menu never displayed to the client for whatever reason.
- MenuCancel_Timeout - The menu timed out. Not called if the menu time was MENU_TIME_FOREVER.
- MenuCancel_ExitBack - The client selected Back. Only called if SetMenuExitBack has been called and set to true before the menu was sent. Not called if SetMenuExit was set to false.
MenuAction_End
case MenuAction_End: { delete menu; }
- param1: MenuEnd reason
- param2: If param1 is MenuEnd_Cancelled, the MenuCancel reason
- return: 0 (or don't return)
MenuAction_End is called when all clients have closed a menu or vote. For menus that are not going to be redisplayed, it is required that you call CloseHandle on the menu here.
The parameters are rarely used in MenuAction_End. param1 is the menu end reason. param2 depends on param1.
The end reasons you can receive for normal menus are:
- MenuEnd_Selected - The menu closed because an item was selected (MenuAction_Select was fired)
- MenuEnd_Cancelled - The menu was cancelled (MenuAction_Cancel was fired), cancel reason is in param2; cancel reason can be any of the ones listed in MenuAction_Cancel except MenuCancel_Exit or MenuCancel_ExitBack
- MenuEnd_Exit - The menu was exited via the Exit item (MenuAction_Cancel was fired with param2 set to MenuCancel_Exit)
- MenuEnd_ExitBack - The menu was exited via the ExitBack item (MenuAction_Cancel was fired with param 2 set to MenuCancel_ExitBack)
Note: You do not have the client index during this callback, so it's far too late to do anything useful with this information.
MenuAction_DrawItem
case MenuAction_DrawItem: { int style; char info[32]; menu.GetItem(param2, info, sizeof(info), style); if (StrEqual(info, CHOICE3)) { return ITEMDRAW_DISABLED; } else { return style; } }
- param1: client index
- param2: item number for use with GetMenuItem
- return: new ITEMDRAW properties or style from GetMenuItem. Since 0 is ITEMDRAW_DEFAULT, returning 0 clears all styles for this item.
MenuAction_DrawItem is called once for each item on the menu for each user. You can manipulate its draw style here. param1 is the client, param2 is the menu position.
Using the item position to check which item was selected is a bad idea, as item position is brittle and will break things if AddMenuItem or InsertMenuItem is used. It is recommended that you instead use the Menu item's info string, as done in the code above.
GetMenuItem is used here to fetch the info string and menu style.
You should return the style you want the menu item to have. In our example, if client 1 is viewing the menu, we want CHOICE3 to be disabled.
the return value is a bitfield, so to apply multiple styles, you do something like this:
return ITEMDRAW_NOTEXT | ITEMDRAW_SPACER;
Failing to return the current item's style if you don't change the style is a programmer error.
MenuAction_DisplayItem
case MenuAction_DisplayItem: { char info[32]; menu.GetItem(param2, info, sizeof(info)); char display[64]; if (StrEqual(info, CHOICE3)) { Format(display, sizeof(display), "%T", "Choice 3", param1); return RedrawMenuItem(display); } }
- param1: client index
- param2: item number for use with GetMenuItem
- return: return value from RedrawMenuItem or 0 for no change
MenuAction_DisplayItem is called once for each item on the menu for each user. You can manipulate its text here. param1 is the client, param2 is the menu position.
This callback is intended for use with the Translation system.
Using the item position to check which item was selected is a bad idea, as item position is brittle and will break things if AddMenuItem or InsertMenuItem is used. It is recommended that you instead use the Menu item's info string, as done in the code above.
GetMenuItem is used here to fetch the info string.
Once we have the info string, we compare our item to it and apply the appropriate translation string.
If we change an item, we have to call RedrawMenuItem and return the value it returns. If we do not change an item, we must return 0.
return
return 0;
If you handle MenuAction_DrawItem or MenuAction_DisplayItem, you will get the following warning if you fail to return 0 after the switch block:
warning 209: function "MenuHandler1" should return a value
This is because MenuAction_DrawItem and MenuAction_DisplayItem have return values, while the other actions only return 0.
The End
Hopefully this walk through a simple menu helped you understand why each call is being made where. Menus have some options that we didn't explore here, such as disabling pagination (which is enabled by default). You may want to refer to the main Menu documentation for more details.