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