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