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