]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/menu/menu.qc
make vid_width, vid_height, vid_pixelwidth fully authoritative again, make menu_vid_w...
[xonotic/xonotic-data.pk3dir.git] / qcsrc / menu / menu.qc
1 ///////////////////////////////////////////////
2 // Menu Source File
3 ///////////////////////
4 // This file belongs to dpmod/darkplaces
5 // AK contains all menu functions (especially the required ones)
6 ///////////////////////////////////////////////
7
8 float mouseButtonsPressed;
9 vector menuMousePos;
10 float menuShiftState;
11 float menuPrevTime;
12 float menuAlpha;
13 float menuLogoAlpha;
14 float prevMenuAlpha;
15 float menuInitialized;
16 float menuNotTheFirstFrame;
17 float menuMouseMode;
18
19 void SUB_Null() { }
20
21 void m_sync()
22 {
23         updateCompression();
24         updateConwidths();
25
26         loadAllCvars(main);
27 }
28
29 void m_init()
30 {
31         cvar_set("_menu_alpha", "0");
32         prvm_language = strzone(cvar_string("prvm_language"));
33
34         check_unacceptable_compiler_bugs();
35
36 #ifdef WATERMARK
37         print(sprintf(_("^4MQC Build information: ^1%s\n"), WATERMARK()));
38 #endif
39
40         // list all game dirs (TEST)
41         if(cvar("developer"))
42         {
43                 float i;
44                 string s;
45                 for(i = 0; ; ++i)
46                 {
47                         s = getgamedirinfo(i, GETGAMEDIRINFO_NAME);
48                         if not(s)
49                                 break;
50                         dprint(s, ": ", getgamedirinfo(i, GETGAMEDIRINFO_DESCRIPTION));
51                 }
52         }
53
54         // needs to be done so early because of the constants they create
55         RegisterWeapons();
56         RegisterGametypes();
57
58         float ddsload = cvar("r_texture_dds_load");
59         float texcomp = cvar("gl_texturecompression");
60         updateCompression();
61         if(ddsload != cvar("r_texture_dds_load") || texcomp != cvar("gl_texturecompression"))
62                 localcmd("\nr_restart\n");
63         initConwidths();
64 }
65
66 float MENU_ASPECT = 1.25; // 1280x1024
67 float MENU_MINHEIGHT = 600;
68 float conwidth_s, conheight_s, realconwidth, realconheight, screenconwidth, screenconheight;
69 void draw_reset_cropped()
70 {
71         draw_reset(screenconwidth, screenconheight, 0.5 * (realconwidth - screenconwidth), 0.5 * (realconheight - screenconheight));
72 }
73 void draw_reset_full()
74 {
75         draw_reset(realconwidth, realconheight, 0, 0);
76 }
77 void UpdateConWidthHeight()
78 {
79         conwidth_s = conwidth;
80         conheight_s = conheight;
81         realconwidth = cvar("vid_conwidth");
82         realconheight = cvar("vid_conheight");
83         if(realconwidth / realconheight > MENU_ASPECT)
84         {
85                 // widescreen
86                 conwidth = realconheight * MENU_ASPECT;
87                 conheight = realconheight;
88         }
89         else
90         {
91                 // squarescreen
92                 conwidth = realconwidth;
93                 conheight = realconwidth / MENU_ASPECT;
94         }
95         screenconwidth = conwidth;
96         screenconheight = conheight;
97         if(conwidth < MENU_MINHEIGHT * MENU_ASPECT)
98         {
99                 conheight *= MENU_MINHEIGHT * MENU_ASPECT / conwidth;
100                 conwidth = MENU_MINHEIGHT * MENU_ASPECT;
101         }
102         if(conheight < MENU_MINHEIGHT)
103         {
104                 conwidth *= MENU_MINHEIGHT / conheight;
105                 conheight = MENU_MINHEIGHT;
106         }
107         if(main)
108         {
109                 if(conwidth_s != conwidth || conheight_s != conheight)
110                 {
111                         draw_reset_cropped();
112                         main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
113                 }
114         }
115 }
116
117 void m_init_delayed()
118 {
119         float fh, glob, n, i;
120         string s;
121
122         conwidth = conheight = -1;
123         UpdateConWidthHeight();
124         draw_reset_cropped();
125
126         menuInitialized = 0;
127         if(!preMenuInit())
128                 return;
129         menuInitialized = 1;
130
131         fh = -1;
132         if(cvar_string("menu_skin") != "")
133         {
134                 draw_currentSkin = strcat("gfx/menu/", cvar_string("menu_skin"));
135                 fh = fopen(language_filename(strcat(draw_currentSkin, "/skinvalues.txt")), FILE_READ);
136         }
137         if(fh < 0)
138         if(cvar_defstring("menu_skin") != "")
139         {
140                 cvar_set("menu_skin", cvar_defstring("menu_skin"));
141                 draw_currentSkin = strcat("gfx/menu/", cvar_string("menu_skin"));
142                 fh = fopen(language_filename(strcat(draw_currentSkin, "/skinvalues.txt")), FILE_READ);
143         }
144         if(fh < 0)
145         {
146                 draw_currentSkin = "gfx/menu/default";
147                 fh = fopen(language_filename(strcat(draw_currentSkin, "/skinvalues.txt")), FILE_READ);
148         }
149         if(fh < 0)
150         {
151                 error("cannot load any menu skin\n");
152         }
153         draw_currentSkin = strzone(draw_currentSkin);
154         while((s = fgets(fh)))
155         {
156                 // these two are handled by skinlist.qc
157                 if(substring(s, 0, 6) == "title ")
158                         continue;
159                 if(substring(s, 0, 7) == "author ")
160                         continue;
161                 n = tokenize_console(s);
162                 if(n >= 2)
163                         Skin_ApplySetting(argv(0), substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
164         }
165         fclose(fh);
166
167         glob = search_begin(strcat(draw_currentSkin, "/*.tga"), TRUE, TRUE);
168         if(glob >= 0)
169         {
170                 n = search_getsize(glob);
171                 for(i = 0; i < n; ++i)
172                         precache_pic(search_getfilename(glob, i));
173                 search_end(glob);
174         }
175
176         draw_setMousePointer(SKINGFX_CURSOR, SKINSIZE_CURSOR, SKINOFFSET_CURSOR);
177
178         loadTooltips();
179         anim = spawnAnimHost();
180         main = spawnMainWindow(); main.configureMainWindow(main);
181         unloadTooltips();
182
183         main.resizeNotify(main, '0 0 0', eX * conwidth + eY * conheight, '0 0 0', eX * conwidth + eY * conheight);
184         main.focused = 1;
185         menuShiftState = 0;
186         menuMousePos = '0.5 0.5 0';
187
188         m_sync();
189
190         if(Menu_Active)
191                 m_display(); // delayed menu display
192 }
193
194 void m_keyup (float key, float ascii)
195 {
196         if(!menuInitialized)
197                 return;
198         if(!Menu_Active)
199                 return;
200         draw_reset_cropped();
201         main.keyUp(main, key, ascii, menuShiftState);
202         if(key >= K_MOUSE1 && key <= K_MOUSE3)
203         {
204                 --mouseButtonsPressed;
205                 if(!mouseButtonsPressed)
206                         main.mouseRelease(main, menuMousePos);
207                 if(mouseButtonsPressed < 0)
208                 {
209                         mouseButtonsPressed = 0;
210                         dprint("Warning: released an already released button\n");
211                 }
212         }
213         if(key == K_ALT) menuShiftState -= (menuShiftState & S_ALT);
214         if(key == K_CTRL) menuShiftState -= (menuShiftState & S_CTRL);
215         if(key == K_SHIFT) menuShiftState -= (menuShiftState & S_SHIFT);
216 }
217
218 void m_keydown(float key, float ascii)
219 {
220         if(!menuInitialized)
221                 return;
222         if(!Menu_Active)
223                 return;
224         if(keyGrabber)
225         {
226                 entity e;
227                 e = keyGrabber;
228                 keyGrabber = NULL;
229                 e.keyGrabbed(e, key, ascii);
230         }
231         else
232         {
233                 draw_reset_cropped();
234                 if(key >= K_MOUSE1 && key <= K_MOUSE3)
235                         if(!mouseButtonsPressed)
236                                 main.mousePress(main, menuMousePos);
237                 if(!main.keyDown(main, key, ascii, menuShiftState))
238                         if(key == K_ESCAPE)
239                                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED)) // don't back out to console only
240                                         m_hide(); // disable menu on unhandled ESC
241         }
242         if(key >= K_MOUSE1 && key <= K_MOUSE3)
243         {
244                 ++mouseButtonsPressed;
245                 if(mouseButtonsPressed > 10)
246                 {
247                         mouseButtonsPressed = 10;
248                         dprint("Warning: pressed an already pressed button\n");
249                 }
250         }
251         if(key == K_ALT) menuShiftState |= S_ALT;
252         if(key == K_CTRL) menuShiftState |= S_CTRL;
253         if(key == K_SHIFT) menuShiftState |= S_SHIFT;
254 }
255
256 float SCALEMODE_CROP = 0;
257 float SCALEMODE_LETTERBOX = 1;
258 float SCALEMODE_WIDTH = 2;
259 float SCALEMODE_HEIGHT = 3;
260 float SCALEMODE_STRETCH = 4;
261 void draw_Picture_Aligned(vector algn, float scalemode, string img, float a)
262 {
263         vector sz, org, isz, isz_w, isz_h;
264         float width_is_larger;
265
266         sz = draw_PictureSize(img);
267         width_is_larger = (sz_x * draw_scale_y >= sz_y * draw_scale_x);
268         isz_w = '1 0 0' + '0 1 0' * ((sz_y / sz_x) * (draw_scale_x / draw_scale_y));
269         isz_h = '0 1 0' + '1 0 0' * ((sz_x / sz_y) * (draw_scale_y / draw_scale_x));
270
271         switch(scalemode)
272         {
273                 default:
274                 case SCALEMODE_CROP:
275                         isz = (width_is_larger ? isz_h : isz_w);
276                         break;
277                 case SCALEMODE_LETTERBOX:
278                         isz = (width_is_larger ? isz_w : isz_h);
279                         break;
280                 case SCALEMODE_WIDTH:
281                         isz = isz_w;
282                         break;
283                 case SCALEMODE_HEIGHT:
284                         isz = isz_h;
285                         break;
286                 case SCALEMODE_STRETCH:
287                         isz = '1 1 0';
288                         break;
289         }
290
291         org = eX * (algn_x * (1 - isz_x)) + eY * (algn_y * (1 - isz_y));
292         draw_Picture(org, img, isz, '1 1 1', a);
293 }
294
295 void drawBackground(string img, float a, string algn, float force1)
296 {
297         if(main.mainNexposee.ModalController_state == 0)
298                 return;
299
300         vector v;
301         float i, l;
302         string c;
303         float scalemode;
304
305         v_z = 0;
306
307         scalemode = SCALEMODE_CROP;
308
309         for(i = 0; i < strlen(algn); ++i)
310         {
311                 c = substring(algn, i, 1);
312                 switch(c)
313                 {
314                         case "c": scalemode = SCALEMODE_CROP; goto nopic;
315                         case "l": scalemode = SCALEMODE_LETTERBOX; goto nopic;
316                         case "h": scalemode = SCALEMODE_HEIGHT; goto nopic;
317                         case "w": scalemode = SCALEMODE_WIDTH; goto nopic;
318                         case "s": scalemode = SCALEMODE_STRETCH; goto nopic;
319                         case "1": case "4": case "7": v_x = 0.0; break;
320                         case "2": case "5": case "8": v_x = 0.5; break;
321                         case "3": case "6": case "9": v_x = 1.0; break;
322                         default: v_x = random(); break;
323                 }
324                 switch(c)
325                 {
326                         case "7": case "8": case "9": v_y = 0.0; break;
327                         case "4": case "5": case "6": v_y = 0.5; break;
328                         case "1": case "2": case "3": v_y = 1.0; break;
329                         default: v_y = random(); break;
330                 }
331                 if(l == 0)
332                         draw_Picture_Aligned(v, scalemode, img, a);
333                 else if(force1)
334                         // force all secondary layers to use alpha 1. Prevents ugly issues
335                         // with overlap. It's a flag because it cannot be used for the
336                         // ingame background
337                         draw_Picture_Aligned(v, scalemode, strcat(img, "_l", ftos(l+1)), 1);
338                 else
339                         draw_Picture_Aligned(v, scalemode, strcat(img, "_l", ftos(l+1)), a);
340                 ++l;
341 :nopic
342         }
343 }
344
345 float menu_tooltips;
346 float menu_tooltips_old;
347 vector menuTooltipAveragedMousePos;
348 entity menuTooltipItem;
349 vector menuTooltipOrigin;
350 vector menuTooltipSize;
351 float menuTooltipAlpha;
352 string menuTooltipText;
353 float menuTooltipState; // 0: static, 1: fading in, 2: fading out
354 float m_testmousetooltipbox(vector pos)
355 {
356         if(pos_x >= menuTooltipOrigin_x && pos_x < menuTooltipOrigin_x + menuTooltipSize_x)
357         if(pos_y >= menuTooltipOrigin_y && pos_y < menuTooltipOrigin_y + menuTooltipSize_y)
358                 return FALSE;
359         return TRUE;
360 }
361 float m_testtooltipbox(vector tooltippos)
362 {
363         if(tooltippos_x < 0)
364                 return FALSE;
365         if(tooltippos_y < 0)
366                 return FALSE;
367         if(tooltippos_x + menuTooltipSize_x > 1)
368                 return FALSE;
369         if(tooltippos_y + menuTooltipSize_y > 1)
370                 return FALSE;
371         menuTooltipOrigin = tooltippos;
372         return TRUE;
373 }
374 float m_allocatetooltipbox(vector pos)
375 {
376         vector avoidplus, avoidminus;
377         vector v;
378
379         avoidplus_x = (SKINAVOID_TOOLTIP_x + SKINSIZE_CURSOR_x - SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth;
380         avoidplus_y = (SKINAVOID_TOOLTIP_y + SKINSIZE_CURSOR_y - SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight;
381         avoidplus_z = 0;
382
383         avoidminus_x = (SKINAVOID_TOOLTIP_x + SKINOFFSET_CURSOR_x * SKINSIZE_CURSOR_x) / conwidth + menuTooltipSize_x;
384         avoidminus_y = (SKINAVOID_TOOLTIP_y + SKINOFFSET_CURSOR_y * SKINSIZE_CURSOR_y) / conheight + menuTooltipSize_y;
385         avoidminus_z = 0;
386
387         // bottom right
388         v = pos + avoidplus;
389         if(m_testtooltipbox(v))
390                 return TRUE;
391         
392         // bottom center
393         v_x = pos_x - menuTooltipSize_x * 0.5;
394         if(m_testtooltipbox(v))
395                 return TRUE;
396
397         // bottom left
398         v_x = pos_x - avoidminus_x;
399         if(m_testtooltipbox(v))
400                 return TRUE;
401
402         // top left
403         v_y = pos_y - avoidminus_y;
404         if(m_testtooltipbox(v))
405                 return TRUE;
406
407         // top center
408         v_x = pos_x - menuTooltipSize_x * 0.5;
409         if(m_testtooltipbox(v))
410                 return TRUE;
411         
412         // top right
413         v_x = pos_x + avoidplus_x;
414         if(m_testtooltipbox(v))
415                 return TRUE;
416         
417         return FALSE;
418 }
419 entity m_findtooltipitem(entity root, vector pos)
420 {
421         entity it;
422         entity best;
423
424         best = world;
425         it = root;
426
427         while(it.instanceOfContainer)
428         {
429                 while(it.instanceOfNexposee && it.focusedChild)
430                 {
431                         it = it.focusedChild;
432                         pos = globalToBox(pos, it.Container_origin, it.Container_size);
433                 }
434                 if(it.instanceOfNexposee)
435                 {
436                         it = it.itemFromPoint(it, pos);
437                         if(it.tooltip)
438                                 best = it;
439                         else if(menu_tooltips == 2 && (it.cvarName || it.onClickCommand))
440                                 best = it;
441                         it = world;
442                 }
443                 else if(it.instanceOfModalController)
444                         it = it.focusedChild;
445                 else
446                         it = it.itemFromPoint(it, pos);
447                 if(!it)
448                         break;
449                 if(it.tooltip)
450                         best = it;
451                 else if(menu_tooltips == 2 && (it.cvarName || it.onClickCommand))
452                         best = it;
453                 pos = globalToBox(pos, it.Container_origin, it.Container_size);
454         }
455
456         return best;
457 }
458 string gettooltip()
459 {
460         if (menu_tooltips == 2)
461         {
462                 string s;
463                 if (menuTooltipItem.cvarName)
464                 {
465                         if (getCvarsMulti(menuTooltipItem))
466                                 s = strcat("[", menuTooltipItem.cvarName, " ", getCvarsMulti(menuTooltipItem), "]");
467                         else
468                                 s = strcat("[", menuTooltipItem.cvarName, "]");
469                 }
470                 else if (menuTooltipItem.onClickCommand)
471                         s = strcat("<", menuTooltipItem.onClickCommand, ">");
472                 else
473                         return menuTooltipItem.tooltip;
474                 if (menuTooltipItem.tooltip)
475                         return strcat(menuTooltipItem.tooltip, " ", s);
476                 return s;
477         }
478         return menuTooltipItem.tooltip;
479 }
480 void m_tooltip(vector pos)
481 {
482         float f, i, w;
483         entity it;
484         vector fontsize, p;
485         string s;
486
487         menu_tooltips = cvar("menu_tooltips");
488         if (!menu_tooltips)
489         {
490                 // don't return immediately, fade out the active tooltip first
491                 if (menuTooltipItem == world)
492                         return;
493                 it = world;
494                 menu_tooltips_old = menu_tooltips;
495         }
496         else
497         {
498                 f = bound(0, frametime * 2, 1);
499                 menuTooltipAveragedMousePos = menuTooltipAveragedMousePos * (1 - f) + pos * f;
500                 f = vlen(pos - menuTooltipAveragedMousePos);
501                 if(f < 0.01)
502                         it = m_findtooltipitem(main, pos);
503                 else
504                         it = world;
505         }
506         fontsize = '1 0 0' * (SKINFONTSIZE_TOOLTIP / conwidth) + '0 1 0' * (SKINFONTSIZE_TOOLTIP / conheight);
507
508         // float menuTooltipState; // 0: static, 1: fading in, 2: fading out
509         if(it != menuTooltipItem)
510         {
511                 switch(menuTooltipState)
512                 {
513                         case 0:
514                                 if(menuTooltipItem)
515                                 {
516                                         // another item: fade out first
517                                         menuTooltipState = 2;
518                                 }
519                                 else
520                                 {
521                                         // new item: fade in
522                                         menuTooltipState = 1;
523                                         menuTooltipItem = it;
524
525                                         menuTooltipOrigin_x = -1; // unallocated
526
527                                         if (menuTooltipText)
528                                                 strunzone(menuTooltipText);
529                                         menuTooltipText = strzone(gettooltip());
530
531                                         i = 0;
532                                         w = 0;
533                                         getWrappedLine_remaining = menuTooltipText;
534                                         while(getWrappedLine_remaining)
535                                         {
536                                                 s = getWrappedLine(SKINWIDTH_TOOLTIP, fontsize, draw_TextWidth_WithoutColors);
537                                                 ++i;
538                                                 f = draw_TextWidth(s, FALSE, fontsize);
539                                                 if(f > w)
540                                                         w = f;
541                                         }
542                                         menuTooltipSize_x = w + 2 * (SKINMARGIN_TOOLTIP_x / conwidth);
543                                         menuTooltipSize_y = i * fontsize_y + 2 * (SKINMARGIN_TOOLTIP_y / conheight);
544                                         menuTooltipSize_z = 0;
545                                 }
546                                 break;
547                         case 1:
548                                 // changing item while fading in: fade out first
549                                 menuTooltipState = 2;
550                                 break;
551                         case 2:
552                                 // changing item while fading out: can't
553                                 break;
554                 }
555         }
556         else if(menuTooltipState == 2) // re-fade in?
557                 menuTooltipState = 1;
558
559         if(menuTooltipItem)
560                 if(!m_testmousetooltipbox(pos))
561                         menuTooltipState = 2; // fade out if mouse touches it
562
563         switch(menuTooltipState)
564         {
565                 case 1:
566                         menuTooltipAlpha = bound(0, menuTooltipAlpha + 5 * frametime, 1);
567                         if(menuTooltipAlpha == 1)
568                                 menuTooltipState = 0;
569                         break;
570                 case 2:
571                         menuTooltipAlpha = bound(0, menuTooltipAlpha - 2 * frametime, 1);
572                         if(menuTooltipAlpha == 0)
573                         {
574                                 menuTooltipState = 0;
575                                 menuTooltipItem = world;
576                         }
577                         break;
578         }
579
580         if(menuTooltipItem == world)
581         {
582                 if (menuTooltipText)
583                 {
584                         strunzone(menuTooltipText);
585                         menuTooltipText = string_null;
586                 }
587                 return;
588         }
589         else
590         {
591                 if(menu_tooltips != menu_tooltips_old)
592                 {
593                         if (menu_tooltips != 0 && menu_tooltips_old != 0)
594                                 menuTooltipItem = world; // reload tooltip next frame
595                         menu_tooltips_old = menu_tooltips;
596                 }
597                 else if(menuTooltipOrigin_x < 0) // unallocated?
598                         m_allocatetooltipbox(pos);
599
600                 if(menuTooltipOrigin_x >= 0)
601                 {
602                         // draw the tooltip!
603                         p = SKINBORDER_TOOLTIP;
604                         p_x *= 1 / conwidth;
605                         p_y *= 1 / conheight;
606                         draw_BorderPicture(menuTooltipOrigin, SKINGFX_TOOLTIP, menuTooltipSize, '1 1 1', menuTooltipAlpha, p);
607                         p = menuTooltipOrigin;
608                         p_x += SKINMARGIN_TOOLTIP_x / conwidth;
609                         p_y += SKINMARGIN_TOOLTIP_y / conheight;
610                         getWrappedLine_remaining = menuTooltipText;
611                         while(getWrappedLine_remaining)
612                         {
613                                 s = getWrappedLine(SKINWIDTH_TOOLTIP, fontsize, draw_TextWidth_WithoutColors);
614                                 draw_Text(p, s, fontsize, '1 1 1', SKINALPHA_TOOLTIP * menuTooltipAlpha, FALSE);
615                                 p_y += fontsize_y;
616                         }
617                 }
618         }
619 }
620
621 void m_draw()
622 {
623         float t;
624         float realFrametime;
625
626         execute_next_frame();
627
628         menuMouseMode = cvar("menu_mouse_absolute");
629
630         if (anim)
631                 anim.tickAll(anim);
632
633         if(main)
634                 UpdateConWidthHeight();
635
636         if(!menuInitialized)
637         {
638                 // TODO draw an info image about this situation
639                 m_init_delayed();
640                 return;
641         }
642         if(!menuNotTheFirstFrame)
643         {
644                 menuNotTheFirstFrame = 1;
645                 if(Menu_Active)
646                 if(!cvar("menu_video_played"))
647                 {
648                         localcmd("cd loop $menu_cdtrack; play sound/announcer/default/welcome.ogg\n");
649                         menuLogoAlpha = -0.8; // no idea why, but when I start this at zero, it jumps instead of fading FIXME
650                 }
651                 // ALWAYS set this cvar; if we start but menu is not active, this means we want no background music!
652                 localcmd("set menu_video_played 1\n");
653         }
654
655         t = gettime();
656         realFrametime = frametime = min(0.2, t - menuPrevTime);
657         menuPrevTime = t;
658         time += frametime;
659
660         t = cvar("menu_slowmo");
661         if(t)
662         {
663                 frametime *= t;
664                 realFrametime *= t;
665         }
666         else
667                 t = 1;
668
669         if(Menu_Active)
670         {
671                 if(getmousetarget() == (menuMouseMode ? MT_CLIENT : MT_MENU) && (getkeydest() == KEY_MENU || getkeydest() == KEY_MENU_GRABBED))
672                         setkeydest(keyGrabber ? KEY_MENU_GRABBED : KEY_MENU);
673                 else
674                         m_hide();
675         }
676
677         if(cvar("cl_capturevideo"))
678                 frametime = t / cvar("cl_capturevideo_fps"); // make capturevideo work smoothly
679
680         gamestatus = 0;
681         if(isserver())
682                 gamestatus = gamestatus | GAME_ISSERVER;
683         if(clientstate() == CS_CONNECTED)
684                 gamestatus = gamestatus | GAME_CONNECTED;
685         if(cvar("developer"))
686                 gamestatus = gamestatus | GAME_DEVELOPER;
687
688         prevMenuAlpha = menuAlpha;
689         if(Menu_Active)
690         {
691                 if(menuAlpha == 0 && menuLogoAlpha < 2)
692                 {
693                         menuLogoAlpha = menuLogoAlpha + frametime * 2;
694                 }
695                 else
696                 {
697                         menuAlpha = min(1, menuAlpha + frametime * 5);
698                         menuLogoAlpha = 2;
699                 }
700         }
701         else
702         {
703                 menuAlpha = max(0, menuAlpha - frametime * 5);
704                 menuLogoAlpha = 2;
705         }
706
707         draw_reset_cropped();
708
709         if(!(gamestatus & (GAME_CONNECTED | GAME_ISSERVER)))
710         {
711                 if(menuLogoAlpha > 0)
712                 {
713                         draw_reset_full();
714                         draw_Fill('0 0 0', '1 1 0', SKINCOLOR_BACKGROUND, 1);
715                         drawBackground(SKINGFX_BACKGROUND, bound(0, menuLogoAlpha, 1), SKINALIGN_BACKGROUND, TRUE);
716                         draw_reset_cropped();
717                         if(menuAlpha <= 0 && SKINALPHA_CURSOR_INTRO > 0)
718                         {
719                                 draw_alpha = SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1);
720                                 draw_drawMousePointer(menuMousePos);
721                                 draw_alpha = 1;
722                         }
723                 }
724         }
725         else if(SKINALPHA_BACKGROUND_INGAME)
726         {
727                 if(menuAlpha > 0)
728                 {
729                         draw_reset_full();
730                         drawBackground(SKINGFX_BACKGROUND_INGAME, menuAlpha * SKINALPHA_BACKGROUND_INGAME, SKINALIGN_BACKGROUND_INGAME, FALSE);
731                         draw_reset_cropped();
732                 }
733         }
734
735         if(menuAlpha != prevMenuAlpha)
736                 cvar_set("_menu_alpha", ftos(menuAlpha));
737
738         draw_reset_cropped();
739         preMenuDraw();
740         draw_reset_cropped();
741
742         if(menuAlpha <= 0)
743         {
744                 if(prevMenuAlpha > 0)
745                         main.initializeDialog(main, main.firstChild);
746                 draw_reset_cropped();
747                 postMenuDraw();
748                 return;
749         }
750
751         draw_alpha *= menuAlpha;
752
753         if(menuMouseMode)
754         {
755                 vector newMouse;
756                 newMouse = globalToBox(getmousepos(), draw_shift, draw_scale);
757                 if(newMouse != '0 0 0')
758                         if(newMouse != menuMousePos)
759                         {
760                                 menuMousePos = newMouse;
761                                 if(mouseButtonsPressed)
762                                         main.mouseDrag(main, menuMousePos);
763                                 else
764                                         main.mouseMove(main, menuMousePos);
765                         }
766         }
767         else
768         {
769                 if(frametime > 0)
770                 {
771                         vector dMouse, minpos, maxpos;
772                         dMouse = getmousepos() * (frametime / realFrametime); // for capturevideo
773                         if(dMouse != '0 0 0')
774                         {
775                                 minpos = globalToBox('0 0 0', draw_shift, draw_scale);
776                                 maxpos = globalToBox(eX * (realconwidth - 1) + eY * (realconheight - 1), draw_shift, draw_scale);
777                                 dMouse = globalToBoxSize(dMouse, draw_scale);
778                                 menuMousePos += dMouse * cvar("menu_mouse_speed");
779                                 menuMousePos_x = bound(minpos_x, menuMousePos_x, maxpos_x);
780                                 menuMousePos_y = bound(minpos_y, menuMousePos_y, maxpos_y);
781                                 if(mouseButtonsPressed)
782                                         main.mouseDrag(main, menuMousePos);
783                                 else
784                                         main.mouseMove(main, menuMousePos);
785                         }
786                 }
787         }
788         main.draw(main);
789
790         m_tooltip(menuMousePos);
791
792         draw_alpha = max(draw_alpha, SKINALPHA_CURSOR_INTRO * bound(0, menuLogoAlpha, 1));
793
794         draw_drawMousePointer(menuMousePos);
795
796         draw_reset_cropped();
797         postMenuDraw();
798
799         frametime = 0;
800 }
801
802 void m_display()
803 {
804         Menu_Active = true;
805         setkeydest(KEY_MENU);
806         setmousetarget((menuMouseMode ? MT_CLIENT : MT_MENU));
807
808         if(!menuInitialized)
809                 return;
810
811         if(mouseButtonsPressed)
812                 main.mouseRelease(main, menuMousePos);
813         mouseButtonsPressed = 0;
814
815         main.focusEnter(main);
816         main.showNotify(main);
817 }
818
819 void m_hide()
820 {
821         Menu_Active = false;
822         setkeydest(KEY_GAME);
823         setmousetarget(MT_CLIENT);
824
825         if(!menuInitialized)
826                 return;
827
828         main.focusLeave(main);
829         main.hideNotify(main);
830 }
831
832 void m_toggle(float mode)
833 {
834         if(Menu_Active)
835         {
836                 if (mode == 1)
837                         return;
838                 m_hide();
839         }
840         else
841         {
842                 if (mode == 0)
843                         return;
844                 m_display();
845         }
846 }
847
848 void Shutdown()
849 {
850         entity e;
851
852         m_hide();
853         for(e = NULL; (e = nextent(e)) != NULL; )
854         {
855                 if(e.classname != "vtbl")
856                         if(e.destroy)
857                                 e.destroy(e);
858         }
859 }
860
861 void m_focus_item_chain(entity outermost, entity innermost)
862 {
863         if(innermost.parent != outermost)
864                 m_focus_item_chain(outermost, innermost.parent);
865         innermost.parent.setFocus(innermost.parent, innermost);
866 }
867
868 void m_activate_window(entity wnd)
869 {
870         entity par;
871         par = wnd.parent;
872         if(par)
873                 m_activate_window(par);
874
875         if(par.instanceOfModalController)
876         {
877                 if(wnd.tabSelectingButton)
878                         // tabs
879                         TabButton_Click(wnd.tabSelectingButton, wnd);
880                 else
881                         // root
882                         par.initializeDialog(par, wnd);
883         }
884         else if(par.instanceOfNexposee)
885         {
886                 // nexposee (sorry for violating abstraction here)
887                 par.selectedChild = wnd;
888                 par.animationState = 1;
889                 Container_setFocus(par, NULL);
890         }
891         else if(par.instanceOfContainer)
892         {
893                 // other containers
894                 if(par.focused)
895                         par.setFocus(par, wnd);
896         }
897 }
898
899 void m_setpointerfocus(entity wnd)
900 {
901         if(wnd.instanceOfContainer)
902         {
903                 entity focus = wnd.preferredFocusedGrandChild(wnd);
904                 if(focus)
905                 {
906                         menuMousePos = focus.origin + 0.5 * focus.size;
907                         menuMousePos_x *= 1 / conwidth;
908                         menuMousePos_y *= 1 / conheight;
909                         if(wnd.focused) // why does this never happen?
910                                 m_focus_item_chain(wnd, focus);
911                 }
912         }
913 }
914
915 void m_goto(string itemname)
916 {
917         entity e;
918         if(!menuInitialized)
919                 return;
920         if(itemname == "") // this can be called by GameCommand
921         {
922                 if(gamestatus & (GAME_ISSERVER | GAME_CONNECTED))
923                         m_hide();
924                 else
925                 {
926                         m_activate_window(main.mainNexposee);
927                         m_display();
928                 }
929         }
930         else
931         {
932                 for(e = NULL; (e = find(e, name, itemname)); )
933                         if(e.classname != "vtbl")
934                                 break;
935                 if(e)
936                 {
937                         m_hide();
938                         m_activate_window(e);
939                         m_setpointerfocus(e);
940                         m_display();
941                 }
942         }
943 }
944
945 void m_goto_skin_selector()
946 {
947         if(!menuInitialized)
948                 return;
949         // TODO add code to switch back to the skin selector (no idea how to do it now)
950         m_goto("skinselector");
951 }
952
953 void m_goto_language_selector()
954 {
955         if(!menuInitialized)
956                 return;
957         // TODO add code to switch back to the language selector (no idea how to do it now)
958         m_goto("languageselector");
959 }
960
961 void m_goto_video_settings()
962 {
963         if(!menuInitialized)
964                 return;
965         // TODO add code to switch back to the video settings (no idea how to do it now)
966         m_goto("videosettings");
967 }