Generic Menuing System

 

Well a lot of people seem to want an easy to use and adapt menu system, so here one is. The majority of the code is supplied in the zip at the bottom of the page, but I would suggest you take a look at it so you can find out how it works. I've commented bits which I though were not as clear as they could be. This tut is slightly more invasive than the last few I've posted, but that is unavoidable. There are a couple fo options with the code which I'll mention whe we reach them


First of all include menu.c and menu.h in your project/makefile.

Next find the line #include "game.h" in g_local.h. Add the line #include "menu.h" on the line below it.

Scroll down to the client structure. After the line qboolean update_chase; insert the line below.

// menu system code
        menusystem_t   menustorage;

Thats us finished with g_local.h


Open up g_main.c. Find the function EndDMLevel. After the variable declarations add this line

clearallmenus(ent);

Now we deal with p_view.c Adding the menu causes some problems without these changes. it's to do with the way quake2 handles menus and the escape key.

Go to ClientEndServerFrame. It's at the bottom of the file unless you have changed things. find the code that reads

// if the scoreboard is up, update it
        if (ent->client->showscores && deathmatch->value && !(level.framenum & 31))
{
               DeathmatchScoreboardMessage (ent, ent->enemy);
               gi.unicast (ent, false);
}

and change it to read

// if the scoreboard is up, update it
 
        if (ent->client->showscores && deathmatch->value && !(level.framenum & 31) && !(ent->client->menustorage.menu_active))
        {
               DeathmatchScoreboardMessage (ent, ent->enemy);
               gi.unicast (ent, false);
        }
 
        else if (!ent->client->showscores && (ent->client->menustorage.menu_active))
        {
        closemenu(ent);
        }

On to the next file :)


p_client.c Find ClientConnect add the code below just above the return true; at the bottom of it

initmenu(ent);

short isn't it. Next!!


Ok now we reach an option time. Do you want separate keys for controling the menu, or do you want to use the keys that have been set up to use the inventory? Using the inentory keys keeps things simple, but it's your choice. Pick one of the sections below


Inventory keys

Find the function SelectNextItem in g_cmds.c and insert the code below just after the variable declarations.

        // menu system code
        if (ent->client->menustorage.menu_active)
        {
               menudown(ent);
               return;
        }

now find SelectPrevItem and insert the code below just after the variable declarations.

        // menu system code
        if (ent->client->menustorage.menu_active)
        {
               menuup(ent);
               return;
        }

Finally go to Cmd_InvUse_f and again, just after the variable declarations, add the code below.

// menu system code
        if (ent->client->menustorage.menu_active)
        {
               menuselect(ent);
               return;
        }

Separate keys

paste the code below into g_cmds.c near the top.

 
void Cmd_menu_down (edict_t *ent)
{
        if (ent->client->menustorage.menu_active)
        {
               menudown(ent);
               return;
        }
}
 
void Cmd_menu_up (edict_t *ent)
{
        if (ent->client->menustorage.menu_active)
        {
               menuup(ent);
               return;
        }
}
 
void Cmd_menu_select (edict_t *ent)
{
        if (ent->client->menustorage.menu_active)
        {
               menuselect(ent);
               return;
        }
}

Now add the code below just before the final else in the last function of g_cmds.c

        else if (Q_stricmp (cmd, "menuup") == 0)
             Cmd_menu_up(ent);
        else if (Q_stricmp (cmd, "menudown") == 0)
             Cmd_menu_down(ent);
        else if (Q_stricmp (cmd, "menuselect") == 0)
             Cmd_menu_select(ent);

Bind the keys and you're off :)


I've included a sample menu in menu.c to try it out add

        else if (Q_stricmp (cmd, "menu") == 0)
             Menu_test(ent);

to g_cmds.c just before the last else.

Now for the explanation. To create a menu you want to use a modified copy of the function below.

void Menu_test(edict_t *ent)
{
 
   if (ent->client->showscores || ent->client->showinventory || ent->client->menustorage.menu_active)
        return;
clearmenu(ent);
 
addlinetomenu(ent,"This is a test menu",0);
addlinetomenu(ent,"option 1",1);
 
setmenuhandler(ent,testmenuhandler);
 
ent->client->menustorage.currentline=2;
showmenu(ent);
 
}

The clearmenu is to keep everything tidy and makesure there are no memory conflicts.

addlinetomenu is slightly trickier. the first argument is the entity who's menu you are modifing. the second is the text that gets displayed when you see the menu. The third has two functions. when it is 0 , that is the display nonselectable message flag. when is is anything else, it is the value returned when that option is selected.

setmenuhandler: to let the menus actually do stuff you have to have a function that takes the return value from the menu. you set that here. don't create a menu without one as it will crash the machine. The function has to be of the format void functionname(edict_t *ent,int option) option is the vaule that the menu returns. Take a look at the example in menu.c

there is a very good reason to set ent->client->menustorage.currentline. You set it to the first selectable option on your menu. if you don't then your players will have to move the cursor to select the first option. sorry , but it's a code based limitation. Fix it if you want to.

showmenu just brings the menu up so you can see it.


Thats it :) Mail me if you have any problems. Oh and credit goes to a bunch of people including the makers of qmenu, I coded the menu using their code as a road map. It's functionally similar, with changes added to make it slightly more flexible


Resources


Skunkworks Tutorials