Difference between revisions of "Menus Step By Step (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
(Fixed markup for translations file)
m (Fixed [b] to wiki markup)
Line 315: Line 315:
 
* 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 [b]not[/b] have the client index during this callback, so it's far too late to do anything useful with this information.
+
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 ====
Line 342: Line 342:
 
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.
  
[b]Failing to return the current item's style if you don't change said style is a programmer error.[/b]
+
'''Failing to return the current item's style if you don't change said style is a programmer error.'''
  
 
==== MenuAction_DisplayItem ====
 
==== MenuAction_DisplayItem ====

Revision as of 08:40, 18 October 2012

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.

The working menu example

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 OnPluginStart()
{
	LoadTranslations("menu_test.phrases");
	RegConsoleCmd("menu_test1", Menu_Test1);
}
 
public MenuHandler1(Handle:menu, MenuAction:action, param1, param2)
{
	switch(action)
	{
		case MenuAction_Start:
		{
			PrintToServer("Displaying menu");
		}
 
		case MenuAction_Display:
		{
			SetMenuTitle(menu, "%T", "Menu Title", param1);
			PrintToServer("Client %d was sent menu with panel %x", param1, param2);
		}
 
		case MenuAction_Select:
		{
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info));
			if (param1 == 1 && 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:
		{
			CloseHandle(menu);
		}
 
		case MenuAction_DrawItem:
		{
			new style;
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info), style);
 
			if (param1 == 1 && StrEqual(info, CHOICE3))
			{
				return ITEMDRAW_DISABLED;
			}
			else
			{
				return style;
			}
		}
 
		case MenuAction_DisplayItem:
		{
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info));
 
			decl String:display[64];
 
			if (StrEqual(info, CHOICE3))
			{
				Format(display, sizeof(display), "%T", "Choice 3", param1);
				return RedrawMenuItem(display);
			}
		}
	}
 
	return 0;
}
 
public Action:Menu_Test1(client, args)
{
	new Handle:menu = CreateMenu(MenuHandler1, MENU_ACTIONS_ALL);
	SetMenuTitle(menu, "%T", "Menu Title", LANG_SERVER);
	AddMenuItem(menu, CHOICE1, "Choice 1");
	AddMenuItem(menu, CHOICE2, "Choice 2");
	AddMenuItem(menu, CHOICE3, "Choice 3");
	SetMenuExitButton(menu, false);
	DisplayMenu(menu, 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 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.

CreateMenu

	new Handle:menu = CreateMenu(MenuHandler1, MENU_ACTIONS_ALL);

CreateMenu 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_DisplayIitem. - 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:

	new Handle:menu = CreateMenu(MenuHandler1, MenuAction_Start|MenuAction_Select|MenuAction_Cancel|MenuAction_End);

SetMenuTitle

	SetMenuTitle(menu, "%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.

AddMenuItem

	AddMenuItem(menu, CHOICE1, "Choice 1");
	AddMenuItem(menu, CHOICE2, "Choice 2");
	AddMenuItem(menu, CHOICE3, "Choice 3");

A menu isn't useful without something in it! AddMenuItem has 3 required arguments and one optional argument.

The 3 required arguments are:

The Menu handle, 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.

SetMenuExitButton

 	SetMenuExitButton(menu, false);

Defaults to true.

It set to false, the menu won't have an exit button.

SetMenuExitBackButton

 	SetMenuExitBackButton(menu, 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.

DisplayMenu

 	DisplayMenu(menu, client, 20);

DisplayMenu takes 3 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 third argument.

MenuHandler1

public MenuHandler1(Handle:menu, MenuAction:action, param1, 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 two cell arguments meaning depend on the MenuAction argument.

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_DisplayIitem. We will discuss each of these as we reach their code.

MenuAction_Start

		case MenuAction_Start:
		{
			PrintToServer("Displaying menu");
		}

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:
		{
			SetMenuTitle(menu, "Choose somethin', will ya?");
			PrintToServer("Client %d was sent menu with panel %x", param1, param2);
		}

MenuAction_Display is called once for each user a menu is displayed to. param1 is the client, param2 is the MenuPanel handle. You will likely never need the MenuPanel handle and it is beyond the scope of this page.

SetMenuTitle 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:
		{
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info));
			if (param1 == 1 && StrEqual(info, CHOICE3))
			{
				PrintToServer("Client %d somehow selected %s despite it being disabled", param1, info);
			}
			else
			{
				PrintToServer("Client %d selected %s", param1, info);
			}
		}

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); }

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:
		{
			CloseHandle(menu);
		}

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.

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:
		{
			new style;
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info), style);
 
			if (param1 == 1 && StrEqual(info, CHOICE3))
			{
				return ITEMDRAW_DISABLED;
			}
			else
			{
				return style;
			}
		}

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.

Failing to return the current item's style if you don't change said style is a programmer error.

MenuAction_DisplayItem

		case MenuAction_DisplayItem:
		{
			decl String:info[32];
			GetMenuItem(menu, param2, info, sizeof(info));
 
			decl String:display[64];
 
			if (StrEqual(info, CHOICE3))
			{
				Format(display, sizeof(display), "%T", "Choice 3", param1);
				return RedrawMenuItem(display);
			}
		}

MenuAction_DrawItem 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;

We have a return value at the bottom of the MenuHandler1 block. This is to prevent this compiler warning:

warning 209: function "MenuHandler1" should return a value


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.