In this tutorial, we will develop a library for creating SA:MP menus. We don't bother with the code for actually displaying the result to the end-user, because it is both simple and irrelevant to the main point. We then look at one trick used to make the code vastly simpler to read and write. First, imagine that we have the following top-level "Buy Things" menu displayed to a user:
Buy Things
------------
Weapons
Vehicles
Clothes
If they select "Vehicles", a second menu is opened in its place:
Vehicles
----------
Banshee
Infernus
Boats
Tank
This menu hierarchy will from now on be represented like so:
Buy Things
Weapons
Minigun
Desert Eagle
Knuckle Dusters
Vehicles
Banshee
Infernus
Boats
Predator
Reefer
Tank
Clothes
Hat
(Gloves)
Cap
There are no gloves, so it is a disabled item, represented here by surrounding brackets.
For example, from this hierarchy, you can see that selecting "Boats" from the vehicles menu opens yet another menu to select a boat. This menu system is the one that will be used in all examples (minus, as previously mentioned, display and processing).
A first pass might look like:
new
Menu:menuBuyThings = CreateMenu("Buy Things"),
Menu:menuWeapons = CreateMenu("Weapons"),
Menu:menuVehicles = CreateMenu("Vehicles"),
Menu:menuBoats = CreateMenu("Boats"),
Menu:menuClothes = CreateMenu("Clothes");
AddChildMenu(menuBuyThings, menuWeapons);
AddChildMenu(menuBuyThings, menuVehicles);
AddChildMenu(menuBuyThings, menuClothes);
AddMenuItem(menuWeapons, "Minigun");
AddMenuItem(menuWeapons, "Desert Eagle");
AddMenuItem(menuWeapons, "Knuckle Dusters");
AddMenuItem(menuVehicles, "Banshee");
AddMenuItem(menuVehicles, "Infernus");
AddChildMenu(menuVehicles, menuBoats); // Add the third level menu.
AddMenuItem(menuVehicles, "Tank");
AddMenuItem(menuBoats, "Predator");
AddMenuItem(menuBoats, "Reefer");
AddMenuItem(menuWeapons, "Hat");
AddMenuItem(menuClothes, "Gloves", false); // Disabled (enabled is default).
AddMenuItem(menuClothes, "Cap");Given suitable implementations of those functions, that is a standard method of constructing menus (or indeed any hierarchy). However, we can go better.
However, this code is bulky (lots of repetition), unclear (in that the hierarchy is not at all obvious), and error-prone (spot the intentional bug). It would be nice to create something much more obvious, something along the lines of:
Buy Things
+ (Weapons
+ Minigun
+ Desert Eagle
+ Knuckle Dusters)
+ (Vehicles
+ Banshee
+ Infernus
+ (Boats
+ Predator
+ Reefer)
+ Tank)
+ (Clothes
+ Hat
- Gloves
+ Cap)
That code isn't QUITE possible, but something very close is!
Pawn has operator overloading for tagged variables. We can exploit this to reduce code. Menus already have a tag of Menu:, but lets give each element a tag too, and convert the strings currently in use to a number (because strings can't have operators):
static stock gLastString[64];
MenuItem:StoreAndTagString(str[])
{
// Store the string for later.
strcpy(gLastString, str);
// Return a tag, the data isn't important (but could be).
return MenuItem:0;
}And some macros for smaller code:
#define E StoreAndTagStringNow we write the actual operators. When you try and do:
new
Num:a = Num:1,
Num:b = Num:7,
Num:c = a + b;You can't, because you can't add Num:s, even though the look like numbers. You need to define an operator to do it, which could do anything:
Num:operator+(Num:left, Num:right)
{
return Num:(_:a + 2 * _:b);
}Now c will equal 15 because of the custom addition (with tag overrides to use the default addition operator within that code).
Because operators can do anything, we can use them to construct menus:
Menu:operator+(Menu:parent, Menu:child)
{
AddChildMenu(parent, child);
return parent;
}With that you can "add" menus together, making the right-hand one a child of the left one, and returning the parent (very important):
new
Menu:menuWeapons = CreateMenu("Weapons"),
Menu:menuVehicles = CreateMenu("Vehicles"),
Menu:menuBoats = CreateMenu("Boats"),
Menu:menuClothes = CreateMenu("Clothes"),
Menu:menuBuyThings =
CreateMenu("Buy Things")
+ menuWeapons
+ menuVehicles
+ menuClothes;Because the same parent menu is returned each time, the additions can be chained.
You can also define operators for different combinations of tags:
Menu:operator+(Menu:parent, MenuItem:child)
{
// The data value doesn't matter, only the tag here.
#pragma unused child
AddMenuItem(parent, gLastString);
return parent;
}
Menu:operator-(Menu:parent, MenuItem:child)
{
// A disabled item instead.
#pragma unused child
AddMenuItem(parent, gLastString, false);
return parent;
}That is actually all the code that is required. Suddenly, we can write:
new
Menu:menuWeapons =
CreateMenu("Weapons")
+ E("Minigun")
+ E("Desert Eagle")
+ E("Knuckle Dusters"),
Menu:menuBoats =
CreateMenu("Boats")
+ E("Predator")
+ E("Reefer"),
Menu:menuClothes =
CreateMenu("Clothes")
+ E("Hat")
- E("Gloves")
+ E("Cap"),
Menu:menuVehicles =
CreateMenu("Vehicles")
+ E("Banshee")
+ E("Infernus")
+ menuBoats
+ E("Tank"),
Menu:menuBuyThings =
CreateMenu("Buy Things")
+ menuWeapons
+ menuVehicles
+ menuClothes;Or, using brackets, get even closer to the original clear layout:
new
Menu:menuBuyThings = CreateMenu("Buy Things")
+ (CreateMenu("Weapons")
+ E("Minigun")
+ E("Desert Eagle")
+ E("Knuckle Dusters"))
+ (CreateMenu("Vehicles")
+ E("Banshee")
+ E("Infernus")
+ (CreateMenu("Boats")
+ E("Predator")
+ E("Reefer"))
+ E("Tank"))
+ (CreateMenu("Clothes")
+ E("Hat")
- E("Gloves")
+ E("Cap"))
;Obviously many details have been skipped over here, and further improvements to readability and flexibility can be made, but this was just a brief introduction.