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