Menus are surprisingly complex design issues. Much like timers, they have gone through numerous iterations in both API and implementation throughout my/our development projects.
A menu system can be broken down into two major pieces. The first piece is the part that builds the menu; for example, managing how many items the menu has. The second piece is the part that draws the menu, displaying it to the client, and interpreting the response. For this series, these will respectively be referred to as the “builder” and the “displayer.”
First, let’s start off with the most basic of menu systems: AMX Mod’s. This system was carried over into AMX Mod X, but since AMX Mod X has a newer menu system, we will refer to these as “old menus.” Old menus have no building API; you only get a display function.
The first set of problems manifest from not having a building API. The menu display function, show_menu, is a direct wrapper around the ShowMenu network message. You pass it a giant string, a selectable keys bitmask, and a hold time, and Core will split the string into packets and send the message off.
This sort of wrapper does not constitute a menu API:
- The menu string must be built by the user. This makes cross-mod compatibility cumbersome, as “color” vs “no color” syntaxes and whatnot must be accounted for manually.
- There is no concept of automated pagination or callbacks; users must implement these themselves.
- Since the direct display string is handled by every plugin, highly inconsistent code and display styles appear everywhere.
- Dynamically generated menus require manual string manipulation and selectable key bitmasking.
From an implementation standpoint, it gets worse. Notice that show_menu doesn’t require a callback function. How do you find when your menu gets selected?
Old menus are registered via two functions. The first is register_menuid. This function takes in a string pattern and returns a unique integer, a menu id. The second is register_menucmd. This binds a menu id to a function. When you call show_menu, Core will look through every single string registered with register_menu_id. If it finds one such that it is contained within your show_menu string, it will remember that menu id from the player. Finally, when the player uses “menuselect,” Core will lookup the menu id. Then, it will execute every “menucmd” that is bound to that menu id.
This system is atrocious for two reasons. The first is that it requires every user to have a unique string in their menu display. That is a huge burden on someone with dynamic menus. If the strings aren’t unique, bad things will happen since another command or callback could be assigned. Second, it will break a multi-lingual display. In fact, show_menu has an optional parameter which is a hack around this flaw. This hack lets users create unique menu identifiers that are not related to the menu text itself.
Furthermore, show_menu has no respect for other menus. It will trample whatever is in its way; there is no queuing. It also will not tell you whether or not your menu was trampled by another menu, or whether the client disconnected, or whether the menu timed out client-side.
As you can see, the problems behind this system are legion. The lack of design separation forces users to keep rewriting highly redundant and awkward, complicated code. The poor internal implementation means menus have fundamental flaws and will always be conflicting with each other in multiple ways.
Next article, we’ll see attempts at addressing these problems in later APIs.