Difference between revisions of "Admin Menu (SourceMod Scripting)"

From AlliedModders Wiki
Jump to: navigation, search
m (Updated to 1.7 Syntax)
m (Minor formatting of params)
 
(7 intermediate revisions by 2 users not shown)
Line 22: Line 22:
  
 
Example code is below:
 
Example code is below:
<pawn>
+
<sourcepawn>
 
#include <sourcemod>
 
#include <sourcemod>
  
Line 34: Line 34:
 
public void OnPluginStart()
 
public void OnPluginStart()
 
{
 
{
/* See if the menu plugin is already ready */
+
  /* See if the menu plugin is already ready */
TopMenu topmenu;
+
  TopMenu topmenu;
if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))
+
  if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))
{
+
  {
/* If so, manually fire the callback */
+
    /* If so, manually fire the callback */
OnAdminMenuReady(topmenu);
+
    OnAdminMenuReady(topmenu);
}
+
  }
 
}
 
}
  
public OnLibraryRemoved(const String:name[])
+
public void OnLibraryRemoved(const char[] name)
 
{
 
{
if (StrEqual(name, "adminmenu"))
+
  if (StrEqual(name, "adminmenu", false))
{
+
  {
hAdminMenu = null;
+
    hAdminMenu = null;
}
+
  }
 
}
 
}
  
public void OnAdminMenuReady(TopMenu topmenu)
+
public void OnAdminMenuReady(Handle aTopMenu)
 
{
 
{
/* Try to add the category first, if we want to add one.
+
  TopMenu topmenu = TopMenu.FromHandle(aTopMenu);
  Leave this out, if you don't add a new category. */
 
if(obj_dmcommands == INVALID_TOPMENUOBJECT)
 
{
 
OnAdminMenuCreated(topmenu);
 
}
 
  
/* Block us from being called twice */
+
  /* Try to add the category first, if we want to add one.
if (topmenu == hAdminMenu)
+
    Leave this out, if you don't add a new category. */
{
+
  if (obj_dmcommands == INVALID_TOPMENUOBJECT)
return;
+
  {
}
+
    OnAdminMenuCreated(topmenu);
 +
  }
  
hAdminMenu = topmenu;
+
  /* Block us from being called twice */
+
  if (topmenu == hAdminMenu)
/* :TODO: Add everything to the menu! */
+
  {
}</pawn>
+
    return;
 +
  }
 +
 
 +
  hAdminMenu = topmenu;
 +
 
 +
  /* :TODO: Add everything to the menu! */
 +
}</sourcepawn>
  
 
=Top Menu Objects=
 
=Top Menu Objects=
Line 91: Line 93:
 
For example, let's create a category called "CS:S DM Commands".
 
For example, let's create a category called "CS:S DM Commands".
  
<pawn>
+
<sourcepawn>
 
TopMenuObject obj_dmcommands;
 
TopMenuObject obj_dmcommands;
  
public void OnAdminMenuCreated(TopMenu topmenu)
+
public void OnAdminMenuCreated(Handle aTopMenu)
 
{
 
{
/* Block us from being called twice */
+
  TopMenu topmenu = TopMenu.FromHandle(aTopMenu);
if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)
+
 
{
+
  /* Block us from being called twice */
return;
+
  if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)
}
+
  {
 +
    return;
 +
  }
  
obj_dmcommands = AddToTopMenu(topmenu,
+
  obj_dmcommands = AddToTopMenu(
"CS:S DM Commands",
+
    topmenu,
TopMenuObject_Category,
+
    "CS:S DM Commands",
CategoryHandler,
+
    TopMenuObject_Category,
INVALID_TOPMENUOBJECT);
+
    CategoryHandler,
 +
    INVALID_TOPMENUOBJECT
 +
  );
 
}
 
}
  
public void CategoryHandler(TopMenu topmenu,  
+
public void CategoryHandler(
TopMenuAction action,
+
    TopMenu topmenu,  
TopMenuObject object_id,
+
    TopMenuAction action,
int param,
+
    TopMenuObject object_id,
char[] buffer,
+
    int param,
int maxlength)
+
    char[] buffer,
 +
    int maxlength)
 
{
 
{
if (action == TopMenuAction_DisplayTitle)
+
  switch (action)
{
+
  {
Format(buffer, maxlength, "CS:S DM Commands:");
+
    case TopMenuAction_DisplayTitle:
}
+
    {
else if (action == TopMenuAction_DisplayOption)
+
      strcopy(buffer, maxlength, "CS:S DM Commands:");
{
+
    }
Format(buffer, maxlength, "CS:S DM Commands");
+
    case TopMenuAction_DisplayOption:
}
+
    {
}</pawn>
+
      strcopy(buffer, maxlength, "CS:S DM Commands");
 +
    }
 +
  }
 +
}</sourcepawn>
  
 
Note that a callback can handle more than one category.  If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.
 
Note that a callback can handle more than one category.  If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.
Line 131: Line 141:
 
Items are different from categories in that their selection can be detected.  They must have a parent category.  The code below shows an example of finding an existing category and adding an item to it.
 
Items are different from categories in that their selection can be detected.  They must have a parent category.  The code below shows an example of finding an existing category and adding an item to it.
  
<pawn>
+
<sourcepawn>
 
void AttachAdminMenu()
 
void AttachAdminMenu()
 
{
 
{
/* If the category is third party, it will have its own unique name. */
+
  /* If the category is third party, it will have its own unique name. */
TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);
+
  TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);
  
if (player_commands == INVALID_TOPMENUOBJECT)
+
  if (player_commands == INVALID_TOPMENUOBJECT)
{
+
  {
/* Error! */
+
    /* Error! */
return;
+
    return;
}
+
  }
  
AddToTopMenu(hAdminMenu,  
+
  AddToTopMenu(
"sm_poke",
+
    hAdminMenu,  
TopMenuObject_Item,
+
    "sm_poke",
AdminMenu_Poke,
+
    TopMenuObject_Item,
player_commands,
+
    AdminMenu_Poke,
"sm_poke",
+
    player_commands,
ADMFLAG_SLAY);
+
    "sm_poke",
 +
    ADMFLAG_SLAY
 +
  );
 
}
 
}
  
public void AdminMenu_Poke(TopMenu topmenu,  
+
public void AdminMenu_Poke(
TopMenuAction action,
+
    TopMenu topmenu,  
TopMenuObject object_id,
+
    TopMenuAction action,
int param,
+
    TopMenuObject object_id,
char[] buffer,
+
    int param,
int maxlength)
+
    char[] buffer,
 +
    int maxlength)
 
{
 
{
if (action == TopMenuAction_DisplayOption)
+
  switch (action)
{
+
  {
Format(buffer, maxlength, "Poke");
+
    case TopMenuAction_DisplayOption:
}
+
    {
else if (action == TopMenuAction_SelectOption)
+
      strcopy(buffer, maxlength, "Poke");
{
+
    }
/* Do something! client who selected item is in param */
+
    case TopMenuAction_SelectOption:
}
+
    {
}</pawn>
+
      /* Do something! client who selected item is in param */
 +
    }
 +
  }
 +
}</sourcepawn>
  
 
Note that a callback can handle more than one item.  If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.
 
Note that a callback can handle more than one item.  If that is the desired functionality, you can use the <tt>TopMenuObject</tt> value to see which object is being drawn.

Latest revision as of 08:17, 16 September 2022

The admin menu is a menu available to all in-game administrators. It is a simple, one-level, category-based menu with heavy focus on consistency. The menu is simply a TopMenu, from the TopMenus extension, and is provided by adminmenu.sp.

The menu itself has no items unless external plugins provide them. Thus, it is possible (and recommended) for third party developers to extend the menu by attaching their own functionality to it. This requires a bit of patience, but this article documents most of the steps necessary.

Menu Organization

The menu is organized into categories and items. Categories are top-level selections on the menu. Items are selectable entries inside categories. There are two important rules:

  • There is no nesting. Categories cannot have sub-categories.
  • Only categories can exist at the top level (i.e. items must have a parent category).

All entries on the menu, be they categories or items, are called top menu objects, and each must have a unique name. For simplicity, the default admin menu entries use their respective command names as unique names, however this style is not required or enforced.

Unique names are used to identify items for sorting. For more information, see Admin Menu Configuration.


Interfacing to the Admin Menu

The admin menu plugin can be loaded or unloaded at any time, and accounting for this in your code is important. Generally, you must account for the following cases:

  • The admin menu is unloaded while you are interfaced to it.
  • The admin menu is loaded while your plugin is already loaded.
  • The admin menu is loaded before your plugin is loaded.

You do not need to remove menu items; they are removed automatically when your plugin is unloaded, and temporarily removed while your plugin is paused.

Example code is below:

#include <sourcemod>
 
/* Make the admin menu plugin optional */
#undef REQUIRE_PLUGIN
#include <adminmenu>
 
/* Keep track of the top menu */
TopMenu hAdminMenu = null;
 
public void OnPluginStart()
{
  /* See if the menu plugin is already ready */
  TopMenu topmenu;
  if (LibraryExists("adminmenu") && ((topmenu = GetAdminTopMenu()) != null))
  {
    /* If so, manually fire the callback */
    OnAdminMenuReady(topmenu);
  }
}
 
public void OnLibraryRemoved(const char[] name)
{
  if (StrEqual(name, "adminmenu", false))
  {
    hAdminMenu = null;
  }
}
 
public void OnAdminMenuReady(Handle aTopMenu)
{
  TopMenu topmenu = TopMenu.FromHandle(aTopMenu);
 
  /* Try to add the category first, if we want to add one.
     Leave this out, if you don't add a new category. */
  if (obj_dmcommands == INVALID_TOPMENUOBJECT)
  {
    OnAdminMenuCreated(topmenu);
  }
 
  /* Block us from being called twice */
  if (topmenu == hAdminMenu)
  {
    return;
  }
 
  hAdminMenu = topmenu;
 
  /* :TODO: Add everything to the menu! */
}

Top Menu Objects

Top Menu Objects are entries, either categories or items, on the admin menu (or any top menu). Each has the following properties:

  • ID (OUTPUT): The ID of the object in memory.
  • Name (INPUT): The unique name of the object.
  • Type (INPUT): Either a category or an item.
  • Handler (INPUT): The callback function.
  • Parent (INPUT): Empty for categories, or a category object ID for an item.
  • Override (INPUT): The name of the override associated with the command. For more information, see Overriding Command Access. Overrides don't need to be a command name.
  • Flags (INPUT): Default admin flags that should be associated with the object if the override is not set.

The callback determines how the object is drawn on the menu. If no flags are specified, the item is usable by any admin. If an admin does not have access to an object (including an entire category), it will not be displayed.


Adding Categories

Adding categories is sometimes beneficial for larger plugins with many commands. A category object receives two TopMenuAction callbacks:

  • TopMenuAction_DisplayOption - The category is being displayed.
  • TopMenuAction_DisplayTitle - The category is being displayed as a title.

For example, let's create a category called "CS:S DM Commands".

TopMenuObject obj_dmcommands;
 
public void OnAdminMenuCreated(Handle aTopMenu)
{
  TopMenu topmenu = TopMenu.FromHandle(aTopMenu);
 
  /* Block us from being called twice */
  if (topmenu == hAdminMenu && obj_dmcommands != INVALID_TOPMENUOBJECT)
  {
    return;
  }
 
  obj_dmcommands = AddToTopMenu(
    topmenu,
    "CS:S DM Commands",
    TopMenuObject_Category,
    CategoryHandler,
    INVALID_TOPMENUOBJECT
  );
}
 
public void CategoryHandler(
    TopMenu topmenu, 
    TopMenuAction action,
    TopMenuObject object_id,
    int param,
    char[] buffer,
    int maxlength)
{
  switch (action)
  {
    case TopMenuAction_DisplayTitle:
    {
      strcopy(buffer, maxlength, "CS:S DM Commands:");
    }
    case TopMenuAction_DisplayOption:
    {
      strcopy(buffer, maxlength, "CS:S DM Commands");
    }
  }
}

Note that a callback can handle more than one category. If that is the desired functionality, you can use the TopMenuObject value to see which object is being drawn.

Adding Items

Items are different from categories in that their selection can be detected. They must have a parent category. The code below shows an example of finding an existing category and adding an item to it.

void AttachAdminMenu()
{
  /* If the category is third party, it will have its own unique name. */
  TopMenuObject player_commands = FindTopMenuCategory(hAdminMenu, ADMINMENU_PLAYERCOMMANDS);
 
  if (player_commands == INVALID_TOPMENUOBJECT)
  {
    /* Error! */
    return;
  }
 
  AddToTopMenu(
    hAdminMenu, 
    "sm_poke",
    TopMenuObject_Item,
    AdminMenu_Poke,
    player_commands,
    "sm_poke",
    ADMFLAG_SLAY
  );
}
 
public void AdminMenu_Poke(
    TopMenu topmenu, 
    TopMenuAction action,
    TopMenuObject object_id,
    int param,
    char[] buffer,
    int maxlength)
{
  switch (action)
  {
    case TopMenuAction_DisplayOption:
    {
      strcopy(buffer, maxlength, "Poke");
    }
    case TopMenuAction_SelectOption:
    {
      /* Do something! client who selected item is in param */
    }
  }
}

Note that a callback can handle more than one item. If that is the desired functionality, you can use the TopMenuObject value to see which object is being drawn.

Returning to the Menu

Sometimes it is desirable to re-display the admin menu to the client. This can be achievied via RedisplayAdminMenu in adminmenu.inc.