1 // menus.cpp: ingame menu system (also used for scores and serverlist)
2 
3 #include "engine.h"
4 
5 #define GUI_TITLE_COLOR  0xFFDD88
6 #define GUI_BUTTON_COLOR 0xFFFFFF
7 #define GUI_TEXT_COLOR   0xDDFFDD
8 
9 static vec menupos;
10 static int menustart = 0;
11 static g3d_gui *cgui = NULL;
12 
13 VAR(guitabnum, 1, 0, 0);
14 
15 struct menu : g3d_callback
16 {
17     char *name, *header;
18     uint *contents, *init, *onclear;
19     bool showtab, keeptab;
20     int menutab;
21 
menumenu22     menu() : name(NULL), header(NULL), contents(NULL), init(NULL), onclear(NULL), showtab(true), keeptab(false), menutab(1) {}
23 
guimenu24     void gui(g3d_gui &g, bool firstpass)
25     {
26         cgui = &g;
27         guitabnum = menutab;
28         cgui->start(menustart, 0.03f, showtab ? &menutab : NULL);
29         if(showtab) cgui->tab(header ? header : name, GUI_TITLE_COLOR);
30         execute(contents);
31         cgui->end();
32         cgui = NULL;
33         guitabnum = 0;
34     }
35 
clearmenu36     virtual void clear()
37     {
38         if(onclear) { freecode(onclear); onclear = NULL; }
39     }
40 };
41 
42 struct delayedupdate
43 {
44     enum
45     {
46         INT,
47         FLOAT,
48         STRING,
49         ACTION
50     } type;
51     ident *id;
52     union
53     {
54         int i;
55         float f;
56         char *s;
57     } val;
delayedupdatedelayedupdate58     delayedupdate() : type(ACTION), id(NULL) { val.s = NULL; }
~delayedupdatedelayedupdate59     ~delayedupdate() { if(type == STRING || type == ACTION) DELETEA(val.s); }
60 
scheduledelayedupdate61     void schedule(const char *s) { type = ACTION; val.s = newstring(s); }
scheduledelayedupdate62     void schedule(ident *var, int i) { type = INT; id = var; val.i = i; }
scheduledelayedupdate63     void schedule(ident *var, float f) { type = FLOAT; id = var; val.f = f; }
scheduledelayedupdate64     void schedule(ident *var, char *s) { type = STRING; id = var; val.s = newstring(s); }
65 
getintdelayedupdate66     int getint() const
67     {
68         switch(type)
69         {
70             case INT: return val.i;
71             case FLOAT: return int(val.f);
72             case STRING: return int(strtol(val.s, NULL, 0));
73             default: return 0;
74         }
75     }
76 
getfloatdelayedupdate77     float getfloat() const
78     {
79         switch(type)
80         {
81             case INT: return float(val.i);
82             case FLOAT: return val.f;
83             case STRING: return float(parsefloat(val.s));
84             default: return 0;
85         }
86     }
87 
getstringdelayedupdate88     const char *getstring() const
89     {
90         switch(type)
91         {
92             case INT: return intstr(val.i);
93             case FLOAT: return intstr(int(floor(val.f)));
94             case STRING: return val.s;
95             default: return "";
96         }
97     }
98 
rundelayedupdate99     void run()
100     {
101         if(type == ACTION) { if(val.s) execute(val.s); }
102         else if(id) switch(id->type)
103         {
104             case ID_VAR: setvarchecked(id, getint()); break;
105             case ID_FVAR: setfvarchecked(id, getfloat()); break;
106             case ID_SVAR: setsvarchecked(id, getstring()); break;
107             case ID_ALIAS: alias(id->name, getstring()); break;
108         }
109     }
110 };
111 
112 static hashnameset<menu> guis;
113 static vector<menu *> guistack;
114 static vector<delayedupdate> updatelater;
115 static bool shouldclearmenu = true, clearlater = false;
116 
117 VARP(menudistance,  16, 40,  256);
118 VARP(menuautoclose, 32, 120, 4096);
119 
menuinfrontofplayer()120 vec menuinfrontofplayer()
121 {
122     vec dir;
123     vecfromyawpitch(camera1->yaw, 0, 1, 0, dir);
124     dir.mul(menudistance).add(camera1->o);
125     dir.z -= player->eyeheight-1;
126     return dir;
127 }
128 
popgui()129 void popgui()
130 {
131     menu *m = guistack.pop();
132     m->clear();
133 }
134 
removegui(menu * m)135 void removegui(menu *m)
136 {
137     loopv(guistack) if(guistack[i]==m)
138     {
139         guistack.remove(i);
140         m->clear();
141         return;
142     }
143 }
144 
pushgui(menu * m,int pos=-1)145 void pushgui(menu *m, int pos = -1)
146 {
147     if(guistack.empty())
148     {
149         menupos = menuinfrontofplayer();
150         g3d_resetcursor();
151     }
152     if(pos < 0) guistack.add(m);
153     else guistack.insert(pos, m);
154     if(pos < 0 || pos==guistack.length()-1)
155     {
156         if(!m->keeptab) m->menutab = 1;
157         menustart = totalmillis;
158     }
159     if(m->init) execute(m->init);
160 }
161 
restoregui(int pos)162 void restoregui(int pos)
163 {
164     int clear = guistack.length()-pos-1;
165     loopi(clear) popgui();
166     menustart = totalmillis;
167 }
168 
showgui(const char * name)169 void showgui(const char *name)
170 {
171     menu *m = guis.access(name);
172     if(!m) return;
173     int pos = guistack.find(m);
174     if(pos<0) pushgui(m);
175     else restoregui(pos);
176 }
177 
hidegui(const char * name)178 void hidegui(const char *name)
179 {
180     menu *m = guis.access(name);
181     if(m) removegui(m);
182 }
183 
cleargui(int n)184 int cleargui(int n)
185 {
186     int clear = guistack.length();
187     if(mainmenu && !isconnected(true) && clear > 0 && guistack[0]->name && !strcmp(guistack[0]->name, "main"))
188     {
189         clear--;
190         if(!clear) return 1;
191     }
192     if(n>0) clear = min(clear, n);
193     loopi(clear) popgui();
194     if(!guistack.empty()) restoregui(guistack.length()-1);
195     return clear;
196 }
197 
clearguis(int level=-1)198 void clearguis(int level = -1)
199 {
200     if(level < 0) level = guistack.length();
201     loopvrev(guistack)
202     {
203        menu *m = guistack[i];
204        if(m->onclear)
205        {
206            uint *action = m->onclear;
207            m->onclear = NULL;
208            execute(action);
209            freecode(action);
210        }
211     }
212     cleargui(level);
213 }
214 
guionclear(char * action)215 void guionclear(char *action)
216 {
217     if(guistack.empty()) return;
218     menu *m = guistack.last();
219     if(m->onclear) { freecode(m->onclear); m->onclear = NULL; }
220     if(action[0]) m->onclear = compilecode(action);
221 }
222 
guistayopen(uint * contents)223 void guistayopen(uint *contents)
224 {
225     bool oldclearmenu = shouldclearmenu;
226     shouldclearmenu = false;
227     execute(contents);
228     shouldclearmenu = oldclearmenu;
229 }
230 
guinoautotab(uint * contents)231 void guinoautotab(uint *contents)
232 {
233     if(!cgui) return;
234     bool oldval = cgui->allowautotab(false);
235     execute(contents);
236     cgui->allowautotab(oldval);
237 }
238 
guimerge(uint * contents)239 void guimerge(uint *contents)
240 {
241     if(!cgui) return;
242     bool oldval = cgui->mergehits(true);
243     execute(contents);
244     cgui->mergehits(oldval);
245 }
246 
247 //@DOC name and icon are optional
guibutton(char * name,char * action,char * icon)248 void guibutton(char *name, char *action, char *icon)
249 {
250     if(!cgui) return;
251     bool hideicon = !strcmp(icon, "0");
252     int ret = cgui->button(name, GUI_BUTTON_COLOR, hideicon ? NULL : (icon[0] ? icon : (strstr(action, "showgui") ? "menu" : "action")));
253     if(ret&G3D_UP)
254     {
255         updatelater.add().schedule(action[0] ? action : name);
256         if(shouldclearmenu) clearlater = true;
257     }
258     else if(ret&G3D_ROLLOVER)
259     {
260         alias("guirollovername", name);
261         alias("guirolloveraction", action);
262     }
263 }
264 
guiimage(char * path,char * action,float * scale,int * overlaid,char * alt,char * title)265 void guiimage(char *path, char *action, float *scale, int *overlaid, char *alt, char *title)
266 {
267     if(!cgui) return;
268     Texture *t = textureload(path, 0, true, false);
269     if(t==notexture)
270     {
271         if(alt[0]) t = textureload(alt, 0, true, false);
272         if(t==notexture) return;
273     }
274     int ret = cgui->image(t, *scale, *overlaid!=0 ? title : NULL);
275     if(ret&G3D_UP)
276     {
277         if(*action)
278         {
279             updatelater.add().schedule(action);
280             if(shouldclearmenu) clearlater = true;
281         }
282     }
283     else if(ret&G3D_ROLLOVER)
284     {
285         alias("guirolloverimgpath", path);
286         alias("guirolloverimgaction", action);
287     }
288 }
289 
guicolor(int * color)290 void guicolor(int *color)
291 {
292     if(cgui)
293     {
294         defformatstring(desc, "0x%06X", *color);
295         cgui->text(desc, *color, NULL);
296     }
297 }
298 
guitextbox(char * text,int * width,int * height,int * color)299 void guitextbox(char *text, int *width, int *height, int *color)
300 {
301     if(cgui && text[0]) cgui->textbox(text, *width ? *width : 12, *height ? *height : 1, *color ? *color : 0xFFFFFF);
302 }
303 
guitext(char * name,char * icon)304 void guitext(char *name, char *icon)
305 {
306     bool hideicon = !strcmp(icon, "0");
307     if(cgui) cgui->text(name, !hideicon && icon[0] ? GUI_BUTTON_COLOR : GUI_TEXT_COLOR, hideicon ? NULL : (icon[0] ? icon : "info"));
308 }
309 
guititle(char * name)310 void guititle(char *name)
311 {
312     if(cgui) cgui->title(name, GUI_TITLE_COLOR);
313 }
314 
guitab(char * name)315 void guitab(char *name)
316 {
317     if(cgui) cgui->tab(name, GUI_TITLE_COLOR);
318 }
319 
guibar()320 void guibar()
321 {
322     if(cgui) cgui->separator();
323 }
324 
guistrut(float * strut,int * alt)325 void guistrut(float *strut, int *alt)
326 {
327     if(cgui)
328     {
329         if(*alt) cgui->strut(*strut); else cgui->space(*strut);
330     }
331 }
332 
guispring(int * weight)333 void guispring(int *weight)
334 {
335     if(cgui) cgui->spring(max(*weight, 1));
336 }
337 
guicolumn(int * col)338 void guicolumn(int *col)
339 {
340     if(cgui) cgui->column(*col);
341 }
342 
updateval(char * var,T val,char * onchange)343 template<class T> static void updateval(char *var, T val, char *onchange)
344 {
345     ident *id = writeident(var);
346     updatelater.add().schedule(id, val);
347     if(onchange[0]) updatelater.add().schedule(onchange);
348 }
349 
getval(char * var)350 static int getval(char *var)
351 {
352     ident *id = readident(var);
353     if(!id) return 0;
354     switch(id->type)
355     {
356         case ID_VAR: return *id->storage.i;
357         case ID_FVAR: return int(*id->storage.f);
358         case ID_SVAR: return parseint(*id->storage.s);
359         case ID_ALIAS: return id->getint();
360         default: return 0;
361     }
362 }
363 
getfval(char * var)364 static float getfval(char *var)
365 {
366     ident *id = readident(var);
367     if(!id) return 0;
368     switch(id->type)
369     {
370         case ID_VAR: return *id->storage.i;
371         case ID_FVAR: return *id->storage.f;
372         case ID_SVAR: return parsefloat(*id->storage.s);
373         case ID_ALIAS: return id->getfloat();
374         default: return 0;
375     }
376 }
377 
getsval(char * var)378 static const char *getsval(char *var)
379 {
380     ident *id = readident(var);
381     if(!id) return "";
382     switch(id->type)
383     {
384         case ID_VAR: return intstr(*id->storage.i);
385         case ID_FVAR: return floatstr(*id->storage.f);
386         case ID_SVAR: return *id->storage.s;
387         case ID_ALIAS: return id->getstr();
388         default: return "";
389     }
390 }
391 
guislider(char * var,int * min,int * max,char * onchange)392 void guislider(char *var, int *min, int *max, char *onchange)
393 {
394 	if(!cgui) return;
395     int oldval = getval(var), val = oldval, vmin = *max > INT_MIN ? *min : getvarmin(var), vmax = *max > INT_MIN ? *max : getvarmax(var);
396     cgui->slider(val, vmin, vmax, GUI_TITLE_COLOR);
397     if(val != oldval) updateval(var, val, onchange);
398 }
399 
guilistslider(char * var,char * list,char * onchange)400 void guilistslider(char *var, char *list, char *onchange)
401 {
402     if(!cgui) return;
403     vector<int> vals;
404     list += strspn(list, "\n\t ");
405     while(*list)
406     {
407         vals.add(parseint(list));
408         list += strcspn(list, "\n\t \0");
409         list += strspn(list, "\n\t ");
410     }
411     if(vals.empty()) return;
412     int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset;
413     loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; }
414     cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, intstr(val));
415     if(offset != oldoffset) updateval(var, vals[offset], onchange);
416 }
417 
guinameslider(char * var,char * names,char * list,char * onchange)418 void guinameslider(char *var, char *names, char *list, char *onchange)
419 {
420     if(!cgui) return;
421     vector<int> vals;
422     list += strspn(list, "\n\t ");
423     while(*list)
424     {
425         vals.add(parseint(list));
426         list += strcspn(list, "\n\t \0");
427         list += strspn(list, "\n\t ");
428     }
429     if(vals.empty()) return;
430     int val = getval(var), oldoffset = vals.length()-1, offset = oldoffset;
431     loopv(vals) if(val <= vals[i]) { oldoffset = offset = i; break; }
432     char *label = indexlist(names, offset);
433     cgui->slider(offset, 0, vals.length()-1, GUI_TITLE_COLOR, label);
434     if(offset != oldoffset) updateval(var, vals[offset], onchange);
435     delete[] label;
436 }
437 
guicheckbox(char * name,char * var,float * on,float * off,char * onchange)438 void guicheckbox(char *name, char *var, float *on, float *off, char *onchange)
439 {
440     bool enabled = getfval(var)!=*off;
441     if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP)
442     {
443         updateval(var, enabled ? *off : (*on || *off ? *on : 1.0f), onchange);
444     }
445 }
446 
guiradio(char * name,char * var,float * n,char * onchange)447 void guiradio(char *name, char *var, float *n, char *onchange)
448 {
449     bool enabled = getfval(var)==*n;
450     if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "radio_on" : "radio_off")&G3D_UP)
451     {
452         if(!enabled) updateval(var, *n, onchange);
453     }
454 }
455 
guibitfield(char * name,char * var,int * mask,char * onchange)456 void guibitfield(char *name, char *var, int *mask, char *onchange)
457 {
458     int val = getval(var);
459     bool enabled = (val & *mask) != 0;
460     if(cgui && cgui->button(name, GUI_BUTTON_COLOR, enabled ? "checkbox_on" : "checkbox_off")&G3D_UP)
461     {
462         updateval(var, enabled ? val & ~*mask : val | *mask, onchange);
463     }
464 }
465 
466 //-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width
guifield(char * var,int * maxlength,char * onchange)467 void guifield(char *var, int *maxlength, char *onchange)
468 {
469     if(!cgui) return;
470     const char *initval = getsval(var);
471 	char *result = cgui->field(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, 0, initval);
472     if(result) updateval(var, result, onchange);
473 }
474 
475 //-ve maxlength indicates a wrapped text field of any (approx 260 chars) length, |maxlength| is the field width
guieditor(char * name,int * maxlength,int * height,int * mode)476 void guieditor(char *name, int *maxlength, int *height, int *mode)
477 {
478     if(!cgui) return;
479     cgui->field(name, GUI_BUTTON_COLOR, *maxlength ? *maxlength : 12, *height, NULL, *mode<=0 ? EDITORFOREVER : *mode);
480     //returns a non-NULL pointer (the currentline) when the user commits, could then manipulate via text* commands
481 }
482 
483 //-ve length indicates a wrapped text field of any (approx 260 chars) length, |length| is the field width
guikeyfield(char * var,int * maxlength,char * onchange)484 void guikeyfield(char *var, int *maxlength, char *onchange)
485 {
486     if(!cgui) return;
487     const char *initval = getsval(var);
488     char *result = cgui->keyfield(var, GUI_BUTTON_COLOR, *maxlength ? *maxlength : -8, 0, initval);
489     if(result) updateval(var, result, onchange);
490 }
491 
492 //use text<action> to do more...
493 
494 
guilist(uint * contents)495 void guilist(uint *contents)
496 {
497     if(!cgui) return;
498     cgui->pushlist();
499     execute(contents);
500     cgui->poplist();
501 }
502 
guialign(int * align,uint * contents)503 void guialign(int *align, uint *contents)
504 {
505     if(!cgui) return;
506     cgui->pushlist();
507     if(*align >= 0) cgui->spring();
508     execute(contents);
509     if(*align == 0) cgui->spring();
510     cgui->poplist();
511 }
512 
newgui(char * name,char * contents,char * header,char * init)513 void newgui(char *name, char *contents, char *header, char *init)
514 {
515     menu *m = guis.access(name);
516     if(!m)
517     {
518         name = newstring(name);
519         m = &guis[name];
520         m->name = name;
521     }
522     else
523     {
524         DELETEA(m->header);
525         freecode(m->contents);
526         freecode(m->init);
527     }
528     if(header && header[0])
529     {
530         char *end = NULL;
531         int val = strtol(header, &end, 0);
532         if(end && !*end)
533         {
534             m->header = NULL;
535             m->showtab = val != 0;
536         }
537         else
538         {
539             m->header = newstring(header);
540             m->showtab = true;
541         }
542     }
543     else
544     {
545         m->header = NULL;
546         m->showtab = true;
547     }
548     m->contents = compilecode(contents);
549     m->init = init && init[0] ? compilecode(init) : NULL;
550 }
551 
552 menu *guiserversmenu = NULL;
553 
guiservers(uint * header,int * pagemin,int * pagemax)554 void guiservers(uint *header, int *pagemin, int *pagemax)
555 {
556     extern const char *showservers(g3d_gui *cgui, uint *header, int pagemin, int pagemax);
557     if(cgui)
558     {
559         const char *command = showservers(cgui, header, *pagemin, *pagemax > 0 ? *pagemax : INT_MAX);
560         if(command)
561         {
562             updatelater.add().schedule(command);
563             if(shouldclearmenu) clearlater = true;
564             guiserversmenu = clearlater || guistack.empty() ? NULL : guistack.last();
565         }
566     }
567 }
568 
notifywelcome()569 void notifywelcome()
570 {
571     if(guiserversmenu)
572     {
573         if(guistack.length() && guistack.last() == guiserversmenu) clearguis();
574         guiserversmenu = NULL;
575     }
576 }
577 
578 COMMAND(newgui, "ssss");
579 COMMAND(guibutton, "sss");
580 COMMAND(guitext, "ss");
581 COMMAND(guiservers, "eii");
582 ICOMMAND(cleargui, "i", (int *n), intret(cleargui(*n)));
583 COMMAND(showgui, "s");
584 COMMAND(hidegui, "s");
585 COMMAND(guionclear, "s");
586 COMMAND(guistayopen, "e");
587 COMMAND(guinoautotab, "e");
588 COMMAND(guimerge, "e");
589 ICOMMAND(guikeeptab, "b", (int *keeptab), if(guistack.length()) guistack.last()->keeptab = *keeptab!=0);
590 COMMAND(guilist, "e");
591 COMMAND(guialign, "ie");
592 COMMAND(guititle, "s");
593 COMMAND(guibar,"");
594 COMMAND(guistrut,"fi");
595 COMMAND(guispring, "i");
596 COMMAND(guicolumn, "i");
597 COMMAND(guiimage,"ssfiss");
598 COMMAND(guislider,"sbbs");
599 COMMAND(guilistslider, "sss");
600 COMMAND(guinameslider, "ssss");
601 COMMAND(guiradio,"ssfs");
602 COMMAND(guibitfield, "ssis");
603 COMMAND(guicheckbox, "ssffs");
604 COMMAND(guitab, "s");
605 COMMAND(guifield, "sis");
606 COMMAND(guikeyfield, "sis");
607 COMMAND(guieditor, "siii");
608 COMMAND(guicolor, "i");
609 COMMAND(guitextbox, "siii");
610 
guiplayerpreview(int * model,int * team,int * weap,char * action,float * scale,int * overlaid,char * title)611 void guiplayerpreview(int *model, int *team, int *weap, char *action, float *scale, int *overlaid, char *title)
612 {
613     if(!cgui) return;
614     int ret = cgui->playerpreview(*model, *team, *weap, *scale, *overlaid!=0 ? title : NULL);
615     if(ret&G3D_UP)
616     {
617         if(*action)
618         {
619             updatelater.add().schedule(action);
620             if(shouldclearmenu) clearlater = true;
621         }
622     }
623 }
624 COMMAND(guiplayerpreview, "iiisfis");
625 
guimodelpreview(char * model,char * animspec,char * action,float * scale,int * overlaid,char * title,int * throttle)626 void guimodelpreview(char *model, char *animspec, char *action, float *scale, int *overlaid, char *title, int *throttle)
627 {
628     if(!cgui) return;
629     int anim = ANIM_ALL;
630     if(animspec[0])
631     {
632         if(isdigit(animspec[0]))
633         {
634             anim = parseint(animspec);
635             if(anim >= 0) anim %= ANIM_INDEX;
636             else anim = ANIM_ALL;
637         }
638         else
639         {
640             vector<int> anims;
641             findanims(animspec, anims);
642             if(anims.length()) anim = anims[0];
643         }
644     }
645     int ret = cgui->modelpreview(model, anim|ANIM_LOOP, *scale, *overlaid!=0 ? title : NULL, *throttle!=0);
646     if(ret&G3D_UP)
647     {
648         if(*action)
649         {
650             updatelater.add().schedule(action);
651             if(shouldclearmenu) clearlater = true;
652         }
653     }
654     else if(ret&G3D_ROLLOVER)
655     {
656         alias("guirolloverpreviewname", model);
657         alias("guirolloverpreviewaction", action);
658     }
659 }
660 COMMAND(guimodelpreview, "sssfisi");
661 
guiprefabpreview(char * prefab,int * color,char * action,float * scale,int * overlaid,char * title,int * throttle)662 void guiprefabpreview(char *prefab, int *color, char *action, float *scale, int *overlaid, char *title, int *throttle)
663 {
664     if(!cgui) return;
665     int ret = cgui->prefabpreview(prefab, vec::hexcolor(*color), *scale, *overlaid!=0 ? title : NULL, *throttle!=0);
666     if(ret&G3D_UP)
667     {
668         if(*action)
669         {
670             updatelater.add().schedule(action);
671             if(shouldclearmenu) clearlater = true;
672         }
673     }
674     else if(ret&G3D_ROLLOVER)
675     {
676         alias("guirolloverpreviewname", prefab);
677         alias("guirolloverpreviewaction", action);
678     }
679 }
680 COMMAND(guiprefabpreview, "sisfisi");
681 
682 struct change
683 {
684     int type;
685     const char *desc;
686 
changechange687     change() {}
changechange688     change(int type, const char *desc) : type(type), desc(desc) {}
689 };
690 static vector<change> needsapply;
691 
692 static struct applymenu : menu
693 {
guiapplymenu694     void gui(g3d_gui &g, bool firstpass)
695     {
696         if(guistack.empty()) return;
697         g.start(menustart, 0.03f);
698         g.text("the following settings have changed:", GUI_TEXT_COLOR, "info");
699         loopv(needsapply) g.text(needsapply[i].desc, GUI_TEXT_COLOR, "info");
700         g.separator();
701         g.text("apply changes now?", GUI_TEXT_COLOR, "info");
702         if(g.button("yes", GUI_BUTTON_COLOR, "action")&G3D_UP)
703         {
704             int changetypes = 0;
705             loopv(needsapply) changetypes |= needsapply[i].type;
706             if(changetypes&CHANGE_GFX) updatelater.add().schedule("resetgl");
707             if(changetypes&CHANGE_SOUND) updatelater.add().schedule("resetsound");
708             clearlater = true;
709         }
710         if(g.button("no", GUI_BUTTON_COLOR, "action")&G3D_UP)
711             clearlater = true;
712         g.end();
713     }
714 
clearapplymenu715     void clear()
716     {
717         menu::clear();
718         needsapply.shrink(0);
719     }
720 } applymenu;
721 
722 VARP(applydialog, 0, 1, 1);
723 
724 static bool processingmenu = false;
725 
addchange(const char * desc,int type)726 void addchange(const char *desc, int type)
727 {
728     if(!applydialog) return;
729     loopv(needsapply) if(!strcmp(needsapply[i].desc, desc)) return;
730     needsapply.add(change(type, desc));
731     if(needsapply.length() && guistack.find(&applymenu) < 0)
732         pushgui(&applymenu, processingmenu ? max(guistack.length()-1, 0) : -1);
733 }
734 
clearchanges(int type)735 void clearchanges(int type)
736 {
737     loopv(needsapply)
738     {
739         if(needsapply[i].type&type)
740         {
741             needsapply[i].type &= ~type;
742             if(!needsapply[i].type) needsapply.remove(i--);
743         }
744     }
745     if(needsapply.empty()) removegui(&applymenu);
746 }
747 
menuprocess()748 void menuprocess()
749 {
750     processingmenu = true;
751     int wasmain = mainmenu, level = guistack.length();
752     loopv(updatelater) updatelater[i].run();
753     updatelater.shrink(0);
754     if(wasmain > mainmenu || clearlater)
755     {
756         if(wasmain > mainmenu || level==guistack.length()) clearguis(level);
757         clearlater = false;
758     }
759     if(mainmenu && !isconnected(true) && guistack.empty()) showgui("main");
760     processingmenu = false;
761 }
762 
763 VAR(mainmenu, 1, 1, 0);
764 
clearmainmenu()765 void clearmainmenu()
766 {
767     if(mainmenu && isconnected())
768     {
769         mainmenu = 0;
770         if(!processingmenu) cleargui();
771     }
772 }
773 
g3d_mainmenu()774 void g3d_mainmenu()
775 {
776     if(!guistack.empty())
777     {
778         extern int usegui2d;
779         if(!mainmenu && !usegui2d && camera1->o.dist(menupos) > menuautoclose) cleargui();
780         else g3d_addgui(guistack.last(), menupos, GUI_2D | GUI_FOLLOW);
781     }
782 }
783 
784