summaryrefslogtreecommitdiff
path: root/src/engine/menus.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/engine/menus.cpp')
-rw-r--r--src/engine/menus.cpp783
1 files changed, 783 insertions, 0 deletions
diff --git a/src/engine/menus.cpp b/src/engine/menus.cpp
new file mode 100644
index 0000000..f2d8e3b
--- /dev/null
+++ b/src/engine/menus.cpp
@@ -0,0 +1,783 @@
+// menus.cpp: ingame menu system (also used for scores and serverlist)
+
+#include "engine.h"
+
+#define GUI_TITLE_COLOR 0xFFDD88
+#define GUI_BUTTON_COLOR 0xFFFFFF
+#define GUI_TEXT_COLOR 0xDDFFDD
+
+static vec menupos;
+static int menustart = 0;
+static g3d_gui *cgui = NULL;
+
+VAR(guitabnum, 1, 0, 0);
+
+struct menu : g3d_callback
+{
+ char *name, *header;
+ uint *contents, *init, *onclear;
+ bool showtab, keeptab;
+ int menutab;
+
+ menu() : name(NULL), header(NULL), contents(NULL), init(NULL), onclear(NULL), showtab(true), keeptab(false), menutab(1) {}
+
+ void gui(g3d_gui &g, bool firstpass)
+ {
+ cgui = &g;
+ guitabnum = menutab;
+ cgui->start(menustart, 0.03f, showtab ? &menutab : NULL);
+ if(showtab) cgui->tab(header ? header : name, GUI_TITLE_COLOR);
+ execute(contents);
+ cgui->end();
+ cgui = NULL;
+ guitabnum = 0;
+ }
+
+ virtual void clear()
+ {
+ if(onclear) { freecode(onclear); onclear = NULL; }
+ }
+};
+
+struct delayedupdate
+{
+ enum
+ {
+ INT,
+ FLOAT,
+ STRING,
+ ACTION
+ } type;
+ ident *id;
+ union
+ {
+ int i;
+ float f;
+ char *s;
+ } val;
+ delayedupdate() : type(ACTION), id(NULL) { val.s = NULL; }
+ ~delayedupdate() { if(type == STRING || type == ACTION) DELETEA(val.s); }
+
+ void schedule(const char *s) { type = ACTION; val.s = newstring(s); }
+ void schedule(ident *var, int i) { type = INT; id = var; val.i = i; }
+ void schedule(ident *var, float f) { type = FLOAT; id = var; val.f = f; }
+ void schedule(ident *var, char *s) { type = STRING; id = var; val.s = newstring(s); }
+
+ int getint() const
+ {
+ switch(type)
+ {
+ case INT: return val.i;
+ case FLOAT: return int(val.f);
+ case STRING: return int(strtol(val.s, NULL, 0));
+ default: return 0;
+ }
+ }
+
+ float getfloat() const
+ {
+ switch(type)
+ {
+ case INT: return float(val.i);
+ case FLOAT: return val.f;
+ case STRING: return float(parsefloat(val.s));
+ default: return 0;
+ }
+ }
+
+ const char *getstring() const
+ {
+ switch(type)
+ {
+ case INT: return intstr(val.i);
+ case FLOAT: return intstr(int(floor(val.f)));
+ case STRING: return val.s;
+ default: return "";
+ }
+ }
+
+ void run()
+ {
+ if(type == ACTION) { if(val.s) execute(val.s); }
+ else if(id) switch(id->type)
+ {
+ case ID_VAR: setvarchecked(id, getint()); break;
+ case ID_FVAR: setfvarchecked(id, getfloat()); break;
+ case ID_SVAR: setsvarchecked(id, getstring()); break;
+ case ID_ALIAS: alias(id->name, getstring()); break;
+ }
+ }
+};
+
+static hashnameset<menu> guis;
+static vector<menu *> guistack;
+static vector<delayedupdate> updatelater;
+static bool shouldclearmenu = true, clearlater = false;
+
+VARP(menudistance, 16, 40, 256);
+VARP(menuautoclose, 32, 120, 4096);
+
+vec menuinfrontofplayer()
+{
+ vec dir;
+ vecfromyawpitch(camera1->yaw, 0, 1, 0, dir);
+ dir.mul(menudistance).add(camera1->o);
+ dir.z -= player->eyeheight-1;
+ return dir;
+}
+
+void popgui()
+{
+ menu *m = guistack.pop();
+ m->clear();
+}
+
+void removegui(menu *m)
+{
+ loopv(guistack) if(guistack[i]==m)
+ {
+ guistack.remove(i);
+ m->clear();
+ return;
+ }
+}
+
+void pushgui(menu *m, int pos = -1)
+{
+ if(guistack.empty())
+ {
+ menupos = menuinfrontofplayer();
+ g3d_resetcursor();
+ }
+ if(pos < 0) guistack.add(m);
+ else guistack.insert(pos, m);
+ if(pos < 0 || pos==guistack.length()-1)
+ {
+ if(!m->keeptab) m->menutab = 1;
+ menustart = totalmillis;
+ }
+ if(m->init) execute(m->init);
+}
+
+void restoregui(int pos)
+{
+ int clear = guistack.length()-pos-1;
+ loopi(clear) popgui();
+ menustart = totalmillis;
+}
+
+void showgui(const char *name)
+{
+ menu *m = guis.access(name);
+ if(!m) return;
+ int pos = guistack.find(m);
+ if(pos<0) pushgui(m);
+ else restoregui(pos);
+}
+
+void hidegui(const char *name)
+{
+ menu *m = guis.access(name);
+ if(m) removegui(m);
+}
+
+int cleargui(int n)
+{
+ int clear = guistack.length();
+ if(mainmenu && !isconnected(true) && clear > 0 && guistack[0]->name && !strcmp(guistack[0]->name, "main"))
+ {
+ clear--;
+ if(!clear) return 1;
+ }
+ if(n>0) clear = min(clear, n);
+ loopi(clear) popgui();
+ if(!guistack.empty()) restoregui(guistack.length()-1);
+ return clear;
+}
+
+void clearguis(int level = -1)
+{
+ if(level < 0) level = guistack.length();
+ loopvrev(guistack)
+ {
+ menu *m = guistack[i];
+ if(m->onclear)
+ {
+ uint *action = m->onclear;
+ m->onclear = NULL;
+ execute(action);
+ freecode(action);
+ }
+ }
+ cleargui(level);
+}
+
+void guionclear(char *action)
+{
+ if(guistack.empty()) return;
+ menu *m = guistack.last();
+ if(m->onclear) { freecode(m->onclear); m->onclear = NULL; }
+ if(action[0]) m->onclear = compilecode(action);
+}
+
+void guistayopen(uint *contents)
+{
+ bool oldclearmenu = shouldclearmenu;
+ shouldclearmenu = false;
+ execute(contents);
+ shouldclearmenu = oldclearmenu;
+}
+
+void guinoautotab(uint *contents)
+{
+ if(!cgui) return;
+ bool oldval = cgui->allowautotab(false);
+ execute(contents);
+ cgui->allowautotab(oldval);
+}
+
+void guimerge(uint *contents)
+{
+ if(!cgui) return;
+ bool oldval = cgui->mergehits(true);
+ execute(contents);
+ cgui->mergehits(oldval);
+}
+
+//@DOC name and icon are optional
+void guibutton(char *name, char *action, char *icon)
+{
+ if(!cgui) return;
+ bool hideicon = !strcmp(icon, "0");
+ int ret = cgui->button(name, GUI_BUTTON_COLOR, hideicon ? NULL : (icon[0] ? icon : (strstr(action, "showgui") ? "menu" : "action")));
+ if(ret&G3D_UP)
+ {
+ updatelater.add().schedule(action[0] ? action : name);
+ if(shouldclearmenu) clearlater = true;
+ }
+ else if(ret&G3D_ROLLOVER)
+ {
+ alias("guirollovername", name);
+ alias("guirolloveraction", action);
+ }
+}
+
+void guiimage(char *path, char *action, float *scale, int *overlaid, char *alt, char *title)
+{
+ if(!cgui) return;
+ Texture *t = textureload(path, 0, true, false);
+ if(t==notexture)
+ {
+ if(alt[0]) t = textureload(alt, 0, true, false);
+ if(t==notexture) return;
+ }
+ int ret = cgui->image(t, *scale, *overlaid!=0 ? title : NULL);
+ if(ret&G3D_UP)
+ {
+ if(*action)
+ {
+ updatelater.add().schedule(action);
+ if(shouldclearmenu) clearlater = true;
+ }
+ }
+ else if(ret&G3D_ROLLOVER)
+ {
+ alias("guirolloverimgpath", path);
+ alias("guirolloverimgaction", action);
+ }
+}
+
+void guicolor(int *color)
+{
+ if(cgui)
+ {
+ defformatstring(desc, "0x%06X", *color);
+ cgui->text(desc, *color, NULL);
+ }
+}
+
+void guitextbox(char *text, int *width, int *height, int *color)
+{
+ if(cgui && text[0]) cgui->textbox(text, *width ? *width : 12, *height ? *height : 1, *color ? *color : 0xFFFFFF);
+}
+
+void guitext(char *name, char *icon)
+{
+ bool hideicon = !strcmp(icon, "0");
+ if(cgui) cgui->text(name, !hideicon && icon[0] ? GUI_BUTTON_COLOR : GUI_TEXT_COLOR, hideicon ? NULL : (icon[0] ? icon : "info"));
+}
+
+void guititle(char *name)
+{
+ if(cgui) cgui->title(name, GUI_TITLE_COLOR);
+}
+
+void guitab(char *name)
+{
+ if(cgui) cgui->tab(name, GUI_TITLE_COLOR);
+}
+
+void guibar()
+{
+ if(cgui) cgui->separator();
+}
+
+void guistrut(float *strut, int *alt)
+{
+ if(cgui)
+ {
+ if(*alt) cgui->strut(*strut); else cgui->space(*strut);
+ }
+}
+
+void guispring(int *weight)
+{
+ if(cgui) cgui->spring(max(*weight, 1));
+}
+
+void guicolumn(int *col)
+{
+ if(cgui) cgui->column(*col);
+}
+
+template<class T> static void updateval(char *var, T val, char *onchange)
+{
+ ident *id = writeident(var);
+ updatelater.add().schedule(id, val);
+ if(onchange[0]) updatelater.add().schedule(onchange);
+}
+
+static int getval(char *var)
+{
+ ident *id = readident(var);
+ if(!id) return 0;
+ switch(id->type)
+ {
+ case ID_VAR: return *id->storage.i;
+ case ID_FVAR: return int(*id->storage.f);
+ case ID_SVAR: return parseint(*id->storage.s);
+ case ID_ALIAS: return id->getint();
+ default: return 0;
+ }
+}
+
+static float getfval(char *var)
+{
+ ident *id = readident(var);
+ if(!id) return 0;
+ switch(id->type)
+ {
+ case ID_VAR: return *id->storage.i;
+ case ID_FVAR: return *id->storage.f;
+ case ID_SVAR: return parsefloat(*id->storage.s);
+ case ID_ALIAS: return id->getfloat();
+ default: return 0;
+ }
+}
+
+static const char *getsval(char *var)
+{
+ ident *id = readident(var);
+ if(!id) return "";
+ switch(id->type)
+ {
+ case ID_VAR: return intstr(*id->storage.i);
+ case ID_FVAR: return floatstr(*id->storage.f);
+ case ID_SVAR: return *id->storage.s;
+ case ID_ALIAS: return id->getstr();
+ default: return "";
+ }
+}
+
+void guislider(char *var, int *min, int *max, char *onchange)
+{
+ if(!cgui) return;
+ int oldval = getval(var), val = oldval, vmin = *max > INT_MIN ? *min : getvarmin(var), vmax = *max > INT_MIN ? *max : getvarmax(var);
+ cgui->slider(val, vmin, vmax, GUI_TITLE_COLOR);
+ if(val != oldval) updateval(var, val, onchange);
+}
+
+void guilistslider(char *var, char *list, char *onchange)
+{
+ if(!cgui) return;
+ vector<int> vals;
+ list += strspn(list, "\n\t ");
+ while(*list)
+ {
+ vals.add(parseint(list));
+ list += strcspn(list, "\n\t \0");
+ list += strspn(list, "\n\t ");
+ }
+ if(vals.empty()) return;
+ int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset;
+ loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; }
+ cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, intstr(val));
+ if(offset != oldoffset) updateval(var, vals[offset], onchange);
+}
+
+void guinameslider(char *var, char *names, char *list, char *onchange)
+{
+ if(!cgui) return;
+ vector<int> vals;
+ list += strspn(list, "\n\t ");
+ while(*list)
+ {
+ vals.add(parseint(list));
+ list += strcspn(list, "\n\t \0");
+ list += strspn(list, "\n\t ");
+ }
+ if(vals.empty()) return;
+ int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset;
+ loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; }
+ char *label = indexlist(names, offset);
+ cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, label);
+ if(offset != oldoffset) updateval(var, vals[offset], onchange);
+ delete[] label;
+}
+
+void guicheckbox(char *name, char *var, float *on, float *off, char *onchange)
+{
+ bool enabled = getfval(var)!=*off;
+ if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP)
+ {
+ updateval(var, enabled ? *off : (*on || *off ? *on : 1.0f), onchange);
+ }
+}
+
+void guiradio(char *name, char *var, float *n, char *onchange)
+{
+ bool enabled = getfval(var)==*n;
+ if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "radio_on" : "radio_off")&G3D_UP)
+ {
+ if(!enabled) updateval(var, *n, onchange);
+ }
+}
+
+void guibitfield(char *name, char *var, int *mask, char *onchange)
+{
+ int val = getval(var);
+ bool enabled = (val & *mask) != 0;
+ if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP)
+ {
+ updateval(var, enabled ? val & ~*mask : val | *mask, onchange);
+ }
+}
+
+//-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width
+void guifield(char *var, int *maxlength, char *onchange)
+{
+ if(!cgui) return;
+ const char *initval = getsval(var);
+ char *result = cgui->field(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, 0, initval);
+ if(result) updateval(var, result, onchange);
+}
+
+//-ve maxlength indicates a wrapped text field of any (approx 260 chars) length, |maxlength| is the field width
+void guieditor(char *name, int *maxlength, int *height, int *mode)
+{
+ if(!cgui) return;
+ cgui->field(name, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, *height, NULL, *mode<=0 ? EDITORFOREVER : *mode);
+ //returns a non-NULL pointer (the currentline) when the user commits, could then manipulate via text* commands
+}
+
+//-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width
+void guikeyfield(char *var, int *maxlength, char *onchange)
+{
+ if(!cgui) return;
+ const char *initval = getsval(var);
+ char *result = cgui->keyfield(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : -8, 0, initval);
+ if(result) updateval(var, result, onchange);
+}
+
+//use text<action> to do more...
+
+
+void guilist(uint *contents)
+{
+ if(!cgui) return;
+ cgui->pushlist();
+ execute(contents);
+ cgui->poplist();
+}
+
+void guialign(int *align, uint *contents)
+{
+ if(!cgui) return;
+ cgui->pushlist();
+ if(*align >= 0) cgui->spring();
+ execute(contents);
+ if(*align == 0) cgui->spring();
+ cgui->poplist();
+}
+
+void newgui(char *name, char *contents, char *header, char *init)
+{
+ menu *m = guis.access(name);
+ if(!m)
+ {
+ name = newstring(name);
+ m = &guis[name];
+ m->name = name;
+ }
+ else
+ {
+ DELETEA(m->header);
+ freecode(m->contents);
+ freecode(m->init);
+ }
+ if(header && header[0])
+ {
+ char *end = NULL;
+ int val = strtol(header, &end, 0);
+ if(end && !*end)
+ {
+ m->header = NULL;
+ m->showtab = val != 0;
+ }
+ else
+ {
+ m->header = newstring(header);
+ m->showtab = true;
+ }
+ }
+ else
+ {
+ m->header = NULL;
+ m->showtab = true;
+ }
+ m->contents = compilecode(contents);
+ m->init = init && init[0] ? compilecode(init) : NULL;
+}
+
+menu *guiserversmenu = NULL;
+
+void guiservers(uint *header, int *pagemin, int *pagemax)
+{
+ extern const char *showservers(g3d_gui *cgui, uint *header, int pagemin, int pagemax);
+ if(cgui)
+ {
+ const char *command = showservers(cgui, header, *pagemin, *pagemax > 0 ? *pagemax : INT_MAX);
+ if(command)
+ {
+ updatelater.add().schedule(command);
+ if(shouldclearmenu) clearlater = true;
+ guiserversmenu = clearlater || guistack.empty() ? NULL : guistack.last();
+ }
+ }
+}
+
+void notifywelcome()
+{
+ if(guiserversmenu)
+ {
+ if(guistack.length() && guistack.last() == guiserversmenu) clearguis();
+ guiserversmenu = NULL;
+ }
+}
+
+COMMAND(newgui, "ssss");
+COMMAND(guibutton, "sss");
+COMMAND(guitext, "ss");
+COMMAND(guiservers, "eii");
+ICOMMAND(cleargui, "i", (int *n), intret(cleargui(*n)));
+COMMAND(showgui, "s");
+COMMAND(hidegui, "s");
+COMMAND(guionclear, "s");
+COMMAND(guistayopen, "e");
+COMMAND(guinoautotab, "e");
+COMMAND(guimerge, "e");
+ICOMMAND(guikeeptab, "b", (int *keeptab), if(guistack.length()) guistack.last()->keeptab = *keeptab!=0);
+COMMAND(guilist, "e");
+COMMAND(guialign, "ie");
+COMMAND(guititle, "s");
+COMMAND(guibar,"");
+COMMAND(guistrut,"fi");
+COMMAND(guispring, "i");
+COMMAND(guicolumn, "i");
+COMMAND(guiimage,"ssfiss");
+COMMAND(guislider,"sbbs");
+COMMAND(guilistslider, "sss");
+COMMAND(guinameslider, "ssss");
+COMMAND(guiradio,"ssfs");
+COMMAND(guibitfield, "ssis");
+COMMAND(guicheckbox, "ssffs");
+COMMAND(guitab, "s");
+COMMAND(guifield, "sis");
+COMMAND(guikeyfield, "sis");
+COMMAND(guieditor, "siii");
+COMMAND(guicolor, "i");
+COMMAND(guitextbox, "siii");
+
+void guiplayerpreview(int *model, int *team, int *weap, char *action, float *scale, int *overlaid, char *title)
+{
+ if(!cgui) return;
+ int ret = cgui->playerpreview(*model, *team, *weap, *scale, *overlaid!=0 ? title : NULL);
+ if(ret&G3D_UP)
+ {
+ if(*action)
+ {
+ updatelater.add().schedule(action);
+ if(shouldclearmenu) clearlater = true;
+ }
+ }
+}
+COMMAND(guiplayerpreview, "iiisfis");
+
+void guimodelpreview(char *model, char *animspec, char *action, float *scale, int *overlaid, char *title, int *throttle)
+{
+ if(!cgui) return;
+ int anim = ANIM_ALL;
+ if(animspec[0])
+ {
+ if(isdigit(animspec[0]))
+ {
+ anim = parseint(animspec);
+ if(anim >= 0) anim %= ANIM_INDEX;
+ else anim = ANIM_ALL;
+ }
+ else
+ {
+ vector<int> anims;
+ findanims(animspec, anims);
+ if(anims.length()) anim = anims[0];
+ }
+ }
+ int ret = cgui->modelpreview(model, anim|ANIM_LOOP, *scale, *overlaid!=0 ? title : NULL, *throttle!=0);
+ if(ret&G3D_UP)
+ {
+ if(*action)
+ {
+ updatelater.add().schedule(action);
+ if(shouldclearmenu) clearlater = true;
+ }
+ }
+ else if(ret&G3D_ROLLOVER)
+ {
+ alias("guirolloverpreviewname", model);
+ alias("guirolloverpreviewaction", action);
+ }
+}
+COMMAND(guimodelpreview, "sssfisi");
+
+void guiprefabpreview(char *prefab, int *color, char *action, float *scale, int *overlaid, char *title, int *throttle)
+{
+ if(!cgui) return;
+ int ret = cgui->prefabpreview(prefab, vec::hexcolor(*color), *scale, *overlaid!=0 ? title : NULL, *throttle!=0);
+ if(ret&G3D_UP)
+ {
+ if(*action)
+ {
+ updatelater.add().schedule(action);
+ if(shouldclearmenu) clearlater = true;
+ }
+ }
+ else if(ret&G3D_ROLLOVER)
+ {
+ alias("guirolloverpreviewname", prefab);
+ alias("guirolloverpreviewaction", action);
+ }
+}
+COMMAND(guiprefabpreview, "sisfisi");
+
+struct change
+{
+ int type;
+ const char *desc;
+
+ change() {}
+ change(int type, const char *desc) : type(type), desc(desc) {}
+};
+static vector<change> needsapply;
+
+static struct applymenu : menu
+{
+ void gui(g3d_gui &g, bool firstpass)
+ {
+ if(guistack.empty()) return;
+ g.start(menustart, 0.03f);
+ g.text("the following settings have changed:", GUI_TEXT_COLOR, "info");
+ loopv(needsapply) g.text(needsapply[i].desc, GUI_TEXT_COLOR, "info");
+ g.separator();
+ g.text("apply changes now?", GUI_TEXT_COLOR, "info");
+ if(g.button("yes", GUI_BUTTON_COLOR, "action")&G3D_UP)
+ {
+ int changetypes = 0;
+ loopv(needsapply) changetypes |= needsapply[i].type;
+ if(changetypes&CHANGE_GFX) updatelater.add().schedule("resetgl");
+ if(changetypes&CHANGE_SOUND) updatelater.add().schedule("resetsound");
+ clearlater = true;
+ }
+ if(g.button("no", GUI_BUTTON_COLOR, "action")&G3D_UP)
+ clearlater = true;
+ g.end();
+ }
+
+ void clear()
+ {
+ menu::clear();
+ needsapply.shrink(0);
+ }
+} applymenu;
+
+VARP(applydialog, 0, 1, 1);
+
+static bool processingmenu = false;
+
+void addchange(const char *desc, int type)
+{
+ if(!applydialog) return;
+ loopv(needsapply) if(!strcmp(needsapply[i].desc, desc)) return;
+ needsapply.add(change(type, desc));
+ if(needsapply.length() && guistack.find(&applymenu) < 0)
+ pushgui(&applymenu, processingmenu ? max(guistack.length()-1, 0) : -1);
+}
+
+void clearchanges(int type)
+{
+ loopv(needsapply)
+ {
+ if(needsapply[i].type&type)
+ {
+ needsapply[i].type &= ~type;
+ if(!needsapply[i].type) needsapply.remove(i--);
+ }
+ }
+ if(needsapply.empty()) removegui(&applymenu);
+}
+
+void menuprocess()
+{
+ processingmenu = true;
+ int wasmain = mainmenu, level = guistack.length();
+ loopv(updatelater) updatelater[i].run();
+ updatelater.shrink(0);
+ if(wasmain > mainmenu || clearlater)
+ {
+ if(wasmain > mainmenu || level==guistack.length()) clearguis(level);
+ clearlater = false;
+ }
+ if(mainmenu && !isconnected(true) && guistack.empty()) showgui("main");
+ processingmenu = false;
+}
+
+VAR(mainmenu, 1, 1, 0);
+
+void clearmainmenu()
+{
+ if(mainmenu && isconnected())
+ {
+ mainmenu = 0;
+ if(!processingmenu) cleargui();
+ }
+}
+
+void g3d_mainmenu()
+{
+ if(!guistack.empty())
+ {
+ extern int usegui2d;
+ if(!mainmenu && !usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui();
+ else g3d_addgui(guistack.last(), menupos, GUI_2D | GUI_FOLLOW);
+ }
+}
+