Merge branch 'master' into Mario/vaporizer_damage
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / quickmenu.qc
1 #include "_all.qh"
2
3 #include "hud_config.qh"
4
5 #include "../dpdefs/keycodes.qh"
6
7 // QUICKMENU_MAXLINES must be <= 10
8 const int QUICKMENU_MAXLINES = 10;
9 // visible entries are loaded from QuickMenu_Buffer into QuickMenu_Page_* arrays
10 string QuickMenu_Page_Command[QUICKMENU_MAXLINES];
11 string QuickMenu_Page_Description[QUICKMENU_MAXLINES];
12 int QuickMenu_Page_Command_Type[QUICKMENU_MAXLINES];
13 int QuickMenu_Page_Entries;
14 int QuickMenu_Page;
15 int QuickMenu_Page_ActivatedEntry = -1;
16 bool QuickMenu_Page_ActivatedEntry_Close;
17 float QuickMenu_Page_ActivatedEntry_Time;
18 bool QuickMenu_IsLastPage;
19 // all the entries are loaded into QuickMenu_Buffer
20 // each entry (submenu or command) is composed of 2 entries
21 const int QUICKMENU_MAXENTRIES = 256;
22 const int QUICKMENU_BUFFER_MAXENTRIES = 2 * QUICKMENU_MAXENTRIES;
23 int QuickMenu_Buffer = -1;
24 int QuickMenu_Buffer_Size;
25 int QuickMenu_Buffer_Index;
26 string QuickMenu_CurrentSubMenu;
27 float QuickMenu_TimeOut;
28
29 // QuickMenu_Buffer are labeled with these tags
30 #define QM_TAG_TITLE "T"
31 #define QM_TAG_SUBMENU "S"
32 #define QM_TAG_COMMAND "C"
33 #define QM_TAG_PLCOMMAND "P"
34
35 #define QuickMenu_Buffer_Set(tag, string) bufstr_set(QuickMenu_Buffer, QuickMenu_Buffer_Size, strcat(tag, string))
36 #define QuickMenu_Buffer_Get() bufstr_get(QuickMenu_Buffer, QuickMenu_Buffer_Index)
37
38 // if s1 is not empty s will be displayed as command otherwise as submenu
39 void QuickMenu_Page_LoadEntry(int i, string s, string s1)
40 {
41         //printf("^xc80 entry %d: %s, %s\n", i, s, s1);
42         if (QuickMenu_Page_Description[i])
43                 strunzone(QuickMenu_Page_Description[i]);
44         QuickMenu_Page_Description[i] = strzone(s);
45         if (QuickMenu_Page_Command[i])
46                 strunzone(QuickMenu_Page_Command[i]);
47         QuickMenu_Page_Command[i] = strzone(s1);
48 }
49
50 void QuickMenu_Page_ClearEntry(int i)
51 {
52         if (QuickMenu_Page_Description[i])
53                 strunzone(QuickMenu_Page_Description[i]);
54         QuickMenu_Page_Description[i] = string_null;
55         if (QuickMenu_Page_Command[i])
56                 strunzone(QuickMenu_Page_Command[i]);
57         QuickMenu_Page_Command[i] = string_null;
58 }
59
60 float QuickMenu_Page_Load(string target_submenu, float new_page);
61 void QuickMenu_Default(string submenu);
62 bool QuickMenu_Open(string mode, string submenu)
63 {
64         int fh = -1;
65         string s;
66
67         if(mode == "")
68         {
69                 if(autocvar_hud_panel_quickmenu_file == "" || autocvar_hud_panel_quickmenu_file == "0")
70                         mode = "default";
71                 else
72                         mode = "file";
73         }
74
75         if(mode == "file")
76         {
77                 if(autocvar_hud_panel_quickmenu_file == "" || autocvar_hud_panel_quickmenu_file == "0")
78                         printf("No file name is set in hud_panel_quickmenu_file, loading default quickmenu\n");
79                 else
80                 {
81                         fh = fopen(autocvar_hud_panel_quickmenu_file, FILE_READ);
82                         if(fh < 0)
83                                 printf("Couldn't open file \"%s\", loading default quickmenu\n", autocvar_hud_panel_quickmenu_file);
84                 }
85                 if(fh < 0)
86                         mode = "default";
87         }
88
89         if(mode == "default")
90         {
91                 QuickMenu_Buffer = buf_create();
92                 if(QuickMenu_Buffer < 0)
93                         return false;
94
95                 QuickMenu_Default(submenu);
96         }
97         else if(mode == "file")
98         {
99                 QuickMenu_Buffer = buf_create();
100                 if(QuickMenu_Buffer < 0)
101                 {
102                         fclose(fh);
103                         return false;
104                 }
105
106                 QuickMenu_Buffer_Size = 0;
107                 while((s = fgets(fh)) && QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES)
108                 {
109                         // first skip invalid entries, so we don't check them anymore
110                         float argc;
111                         argc = tokenize_console(s);
112                         if(argc == 0 || argv(0) == "")
113                                 continue;
114                         if(argc == 1)
115                                 QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
116                         else if(argc == 2)
117                         {
118                                 if(argv(1) == "")
119                                         continue;
120                                 QuickMenu_Buffer_Set(QM_TAG_TITLE, argv(0)); 
121                                 ++QuickMenu_Buffer_Size;
122                                 QuickMenu_Buffer_Set(QM_TAG_COMMAND, argv(1));
123                         }
124                         else if(argc == 3)
125                         {
126                                 // check for special keywords
127                                 float teamplayers = 0, without_me = 0;
128                                 switch(argv(2))
129                                 {
130                                         case "ALLPLAYERS_BUT_ME":               without_me = 1; // fall through
131                                         case "ALLPLAYERS":                              teamplayers = 0; break;
132                                         case "OWNTEAMPLAYERS_BUT_ME":   without_me = 1; // fall through
133                                         case "OWNTEAMPLAYERS":                  teamplayers = 1; break;
134                                         case "ENEMYTEAMPLAYERS":                teamplayers = 2; break;
135                                         default: continue;
136                                 }
137
138                                 if(QuickMenu_Buffer_Size + 3 < QUICKMENU_BUFFER_MAXENTRIES)
139                                 {
140                                         QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
141                                         ++QuickMenu_Buffer_Size;
142                                         QuickMenu_Buffer_Set(QM_TAG_TITLE, strcat(ftos(teamplayers), ftos(without_me))); // put PLCOMMAND arguments in the title string
143                                         ++QuickMenu_Buffer_Size;
144                                         QuickMenu_Buffer_Set(QM_TAG_PLCOMMAND, argv(1));
145                                         ++QuickMenu_Buffer_Size;
146                                         QuickMenu_Buffer_Set(QM_TAG_SUBMENU, argv(0));
147                                 }
148                         }
149                         ++QuickMenu_Buffer_Size;
150                 }
151                 fclose(fh);
152         }
153         else
154         {
155                 printf("Unrecognized mode %s\n", mode);
156                 return false;
157         }
158
159         if (QuickMenu_Buffer_Size <= 0)
160         {
161                 buf_del(QuickMenu_Buffer);
162                 QuickMenu_Buffer = -1;
163                 return false;
164         }
165
166         if(mode == "file")
167                 QuickMenu_Page_Load(submenu, 0);
168         else
169                 QuickMenu_Page_Load("", 0);
170
171         hud_panel_quickmenu = 1;
172         if(autocvar_hud_cursormode)
173                 setcursormode(1);
174         hudShiftState = 0;
175
176         QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
177         return true;
178 }
179
180 void QuickMenu_Buffer_Close()
181 {
182         if (QuickMenu_Buffer >= 0)
183         {
184                 buf_del(QuickMenu_Buffer);
185                 QuickMenu_Buffer = -1;
186                 QuickMenu_Buffer_Size = 0;
187         }
188 }
189
190 void QuickMenu_Close()
191 {
192         if (QuickMenu_CurrentSubMenu)
193                 strunzone(QuickMenu_CurrentSubMenu);
194         QuickMenu_CurrentSubMenu = string_null;
195         int i;
196         for (i = 0; i < QUICKMENU_MAXLINES; ++i)
197                 QuickMenu_Page_ClearEntry(i);
198         QuickMenu_Page_Entries = 0;
199         hud_panel_quickmenu = 0;
200         mouseClicked = 0;
201         prevMouseClicked = 0;
202         QuickMenu_Buffer_Close();
203
204         if(autocvar_hud_cursormode)
205         if(!mv_active)
206                 setcursormode(0);
207 }
208
209 // It assumes submenu open tag is already detected
210 void QuickMenu_skip_submenu(string submenu)
211 {
212         string s, z_submenu;
213         z_submenu = strzone(submenu);
214         for(++QuickMenu_Buffer_Index ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index)
215         {
216                 s = QuickMenu_Buffer_Get();
217                 if(substring(s, 0, 1) != QM_TAG_SUBMENU)
218                         continue;
219                 if(substring(s, 1, -1) == z_submenu) // submenu end
220                         break;
221                 QuickMenu_skip_submenu(substring(s, 1, -1));
222         }
223         strunzone(z_submenu);
224 }
225
226 bool QuickMenu_IsOpened()
227 {
228         return (QuickMenu_Page_Entries > 0);
229 }
230
231 void HUD_Quickmenu_PlayerListEntries(string cmd, int teamplayers, float without_me);
232 bool HUD_Quickmenu_PlayerListEntries_Create(string cmd, int teamplayers, float without_me)
233 {
234         int i;
235         for(i = 0; i < QUICKMENU_MAXLINES; ++i)
236                 QuickMenu_Page_ClearEntry(i);
237         QuickMenu_Buffer_Close();
238
239         QuickMenu_Buffer = buf_create();
240         if(QuickMenu_Buffer < 0)
241                 return false;
242
243         HUD_Quickmenu_PlayerListEntries(cmd, teamplayers, without_me);
244
245         if(QuickMenu_Buffer_Size <= 0)
246         {
247                 buf_del(QuickMenu_Buffer);
248                 QuickMenu_Buffer = -1;
249                 return false;
250         }
251         return true;
252 }
253
254 // new_page 0 means page 0, new_page != 0 means next page
255 int QuickMenu_Buffer_Index_Prev;
256 bool QuickMenu_Page_Load(string target_submenu, int new_page)
257 {
258         string s = string_null, cmd = string_null, z_submenu;
259
260         if (new_page == 0)
261                 QuickMenu_Page = 0;
262         else
263                 ++QuickMenu_Page;
264
265         z_submenu = strzone(target_submenu);
266         if (QuickMenu_CurrentSubMenu)
267                 strunzone(QuickMenu_CurrentSubMenu);
268         QuickMenu_CurrentSubMenu = strzone(z_submenu);
269
270         QuickMenu_IsLastPage = true;
271         QuickMenu_Page_Entries = 0;
272
273         QuickMenu_Buffer_Index = 0;
274         if (z_submenu != "")
275         {
276                 // skip everything until the submenu open tag is found
277                 for( ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index)
278                 {
279                         s = QuickMenu_Buffer_Get();
280                         if(substring(s, 0, 1) == QM_TAG_SUBMENU && substring(s, 1, -1) == z_submenu)
281                         {
282                                 // printf("^3 beginning of %s\n", z_submenu);
283                                 ++QuickMenu_Buffer_Index;
284                                 break; // target_submenu found!
285                         }
286                         // printf("^1 skipping %s\n", s);
287                 }
288                 if(QuickMenu_Buffer_Index == QuickMenu_Buffer_Size)
289                         printf("Couldn't find submenu \"%s\"\n", z_submenu);
290         }
291
292         // only the last page can contain up to QUICKMENU_MAXLINES entries
293         // the other ones contain only (QUICKMENU_MAXLINES - 2) entries
294         // so that the panel can show an empty row and "Continue..."
295         float first_entry = QuickMenu_Page * (QUICKMENU_MAXLINES - 2);
296         int entry_num = 0; // counts entries in target_submenu
297         for( ; QuickMenu_Buffer_Index < QuickMenu_Buffer_Size; ++QuickMenu_Buffer_Index)
298         {
299                 s = QuickMenu_Buffer_Get();
300
301                 if(z_submenu != "" && substring(s, 1, -1) == z_submenu)
302                 {
303                         // printf("^3 end of %s\n", z_submenu);
304                         break;
305                 }
306
307                 if(entry_num >= first_entry)
308                 {
309                         ++QuickMenu_Page_Entries;
310                         if(QuickMenu_Page_Entries == QUICKMENU_MAXLINES - 2)
311                                 QuickMenu_Buffer_Index_Prev = QuickMenu_Buffer_Index;
312                         else if(QuickMenu_Page_Entries == QUICKMENU_MAXLINES)
313                         {
314                                 QuickMenu_Page_ClearEntry(QUICKMENU_MAXLINES - 1);
315                                 QuickMenu_Buffer_Index = QuickMenu_Buffer_Index_Prev;
316                                 QuickMenu_IsLastPage = false;
317                                 break;
318                         }
319                 }
320
321                 // NOTE: entries are loaded starting from 1, not from 0
322                 if(substring(s, 0, 1) == QM_TAG_SUBMENU)
323                 {
324                         if(entry_num >= first_entry)
325                                 QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, substring(s, 1, -1), "");
326                         QuickMenu_skip_submenu(substring(s, 1, -1));
327                 }
328                 else if(entry_num >= first_entry && substring(s, 0, 1) == QM_TAG_TITLE)
329                 {
330                         ++QuickMenu_Buffer_Index;
331                         cmd = QuickMenu_Buffer_Get();
332                         string command_code = substring(cmd, 0, 1);
333                         if(command_code == QM_TAG_COMMAND)
334                                 cmd = substring(cmd, 1, -1);
335                         else if(command_code == QM_TAG_PLCOMMAND)
336                         {
337                                 // throw away the current quickmenu buffer and load a new one
338                                 cmd = substring(cmd, 1, -1);
339                                 strunzone(z_submenu);
340                                 if(HUD_Quickmenu_PlayerListEntries_Create(cmd, stof(substring(s, 1, 1)), stof(substring(s, 2, 1))))
341                                         return QuickMenu_Page_Load("", 0);
342                                 QuickMenu_Close();
343                                 return false;
344                         }
345
346                         tokenize_console(cmd);
347                         QuickMenu_Page_Command_Type[QuickMenu_Page_Entries] = (argv(1) && argv(0) == "toggle");
348
349                         QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, substring(s, 1, -1), cmd);
350                 }
351
352                 ++entry_num;
353         }
354         strunzone(z_submenu);
355         if (QuickMenu_Page_Entries == 0)
356         {
357                 QuickMenu_Close();
358                 return false;
359         }
360         QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
361         return true;
362 }
363
364 bool QuickMenu_ActionForNumber(int num)
365 {
366         if (!QuickMenu_IsLastPage)
367         {
368                 if (num < 0 || num >= QUICKMENU_MAXLINES)
369                         return false;
370                 if (num == QUICKMENU_MAXLINES - 1)
371                         return false;
372                 if (num == 0)
373                 {
374                         QuickMenu_Page_Load(QuickMenu_CurrentSubMenu, +1);
375                         return false;
376                 }
377         } else if (num <= 0 || num > QuickMenu_Page_Entries)
378                 return false;
379
380         if (QuickMenu_Page_Command[num] != "")
381         {
382                 localcmd(strcat("\n", QuickMenu_Page_Command[num], "\n"));
383                 QuickMenu_TimeOut = time + autocvar_hud_panel_quickmenu_time;
384                 return true;
385         }
386         if (QuickMenu_Page_Description[num] != "")
387                 QuickMenu_Page_Load(QuickMenu_Page_Description[num], 0);
388         return false;
389 }
390
391 void QuickMenu_Page_ActiveEntry(float entry_num)
392 {
393         QuickMenu_Page_ActivatedEntry = entry_num;
394         QuickMenu_Page_ActivatedEntry_Time = time + 0.1;
395         if(QuickMenu_Page_Command[QuickMenu_Page_ActivatedEntry])
396         {
397                 bool f = QuickMenu_ActionForNumber(QuickMenu_Page_ActivatedEntry);
398                 // toggle commands don't close the quickmenu
399                 if(QuickMenu_Page_Command_Type[QuickMenu_Page_ActivatedEntry] == 1)
400                         QuickMenu_Page_ActivatedEntry_Close = false;
401                 else
402                         QuickMenu_Page_ActivatedEntry_Close = (f && !(hudShiftState & S_CTRL));
403         }
404         else
405                 QuickMenu_Page_ActivatedEntry_Close = (!(hudShiftState & S_CTRL));
406 }
407
408 bool QuickMenu_InputEvent(float bInputType, float nPrimary, float nSecondary)
409 {
410         // we only care for keyboard events
411         if(bInputType == 2)
412                 return false;
413
414         if(!QuickMenu_IsOpened() || autocvar__hud_configure || mv_active)
415                 return false;
416
417         if(bInputType == 3)
418         {
419                 mousepos.x = nPrimary;
420                 mousepos.y = nSecondary;
421                 return true;
422         }
423
424         // allow console bind to work
425         string con_keys;
426         float keys;
427         con_keys = findkeysforcommand("toggleconsole", 0);
428         keys = tokenize(con_keys); // findkeysforcommand returns data for this
429
430         bool hit_con_bind = false;
431         int i;
432         for (i = 0; i < keys; ++i)
433         {
434                 if(nPrimary == stof(argv(i)))
435                         hit_con_bind = true;
436         }
437
438         if(bInputType == 0) {
439                 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
440                 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
441                 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
442         }
443         else if(bInputType == 1) {
444                 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
445                 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
446                 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
447         }
448
449         if(nPrimary == K_ESCAPE)
450         {
451                 if (bInputType == 1)
452                         return true;
453                 QuickMenu_Close();
454         }
455         else if(nPrimary >= '0' && nPrimary <= '9')
456         {
457                 if (bInputType == 1)
458                         return true;
459                 QuickMenu_Page_ActiveEntry(stof(chr2str(nPrimary)));
460         }
461         if(nPrimary == K_MOUSE1)
462         {
463                 if(bInputType == 0) // key pressed
464                         mouseClicked |= S_MOUSE1;
465                 else if(bInputType == 1) // key released
466                         mouseClicked -= (mouseClicked & S_MOUSE1);
467         }
468         else if(nPrimary == K_MOUSE2)
469         {
470                 if(bInputType == 0) // key pressed
471                         mouseClicked |= S_MOUSE2;
472                 else if(bInputType == 1) // key released
473                         mouseClicked -= (mouseClicked & S_MOUSE2);
474         }
475         else if(hit_con_bind)
476                 return false;
477
478         return true;
479 }
480
481 void QuickMenu_Mouse()
482 {
483         if(mv_active) return;
484
485         if(!mouseClicked)
486         if(prevMouseClicked & S_MOUSE2)
487         {
488                 QuickMenu_Close();
489                 return;
490         }
491
492         if(!autocvar_hud_cursormode)
493         {
494                 mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed;
495
496                 mousepos.x = bound(0, mousepos.x, vid_conwidth);
497                 mousepos.y = bound(0, mousepos.y, vid_conheight);
498         }
499
500         HUD_Panel_UpdateCvars();
501
502         if(panel_bg_padding)
503         {
504                 panel_pos += '1 1 0' * panel_bg_padding;
505                 panel_size -= '2 2 0' * panel_bg_padding;
506         }
507
508         float first_entry_pos, entries_height;
509         vector fontsize;
510         fontsize = '1 1 0' * (panel_size.y / QUICKMENU_MAXLINES);
511         first_entry_pos = panel_pos.y + ((QUICKMENU_MAXLINES - QuickMenu_Page_Entries) * fontsize.y) / 2;
512         entries_height = panel_size.y - ((QUICKMENU_MAXLINES - QuickMenu_Page_Entries) * fontsize.y);
513
514         if (mousepos.x >= panel_pos.x && mousepos.y >= first_entry_pos && mousepos.x <= panel_pos.x + panel_size.x && mousepos.y <= first_entry_pos + entries_height)
515         {
516                 float entry_num;
517                 entry_num = floor((mousepos.y - first_entry_pos) / fontsize.y);
518                 if (QuickMenu_IsLastPage || entry_num != QUICKMENU_MAXLINES - 2)
519                 {
520                         panel_pos.y = first_entry_pos + entry_num * fontsize.y;
521                         vector color;
522                         if(mouseClicked & S_MOUSE1)
523                                 color = '0.5 1 0.5';
524                         else if(hudShiftState & S_CTRL)
525                                 color = '1 1 0.3';
526                         else
527                                 color = '1 1 1';
528                         drawfill(panel_pos, eX * panel_size.x + eY * fontsize.y, color, .2, DRAWFLAG_NORMAL);
529
530                         if(!mouseClicked && (prevMouseClicked & S_MOUSE1))
531                                 QuickMenu_Page_ActiveEntry((entry_num < QUICKMENU_MAXLINES - 1) ? entry_num + 1 : 0);
532                 }
533         }
534
535         vector cursorsize = '32 32 0';
536         drawpic(mousepos, strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursorsize, '1 1 1', 0.8, DRAWFLAG_NORMAL);
537
538         prevMouseClicked = mouseClicked;
539 }
540
541 void HUD_Quickmenu_DrawEntry(vector pos, string desc, string option, vector fontsize)
542 {
543         string entry;
544         float offset;
545         float desc_width = panel_size.x;
546         if(option)
547         {
548                 string pic = strcat(hud_skin_path, "/", option);
549                 if(precache_pic(pic) == "")
550                         pic = strcat("gfx/hud/default/", option);
551                 vector option_size = '1 1 0' * fontsize.y * 0.8;
552                 desc_width -= option_size.x;
553                 drawpic(pos + eX * desc_width + eY * (fontsize.y - option_size.y) / 2, pic, option_size, '1 1 1', panel_fg_alpha, DRAWFLAG_ADDITIVE);
554                 desc_width -= fontsize.x / 4;
555         }
556         entry = textShortenToWidth(desc, desc_width, fontsize, stringwidth_colors);
557         if (autocvar_hud_panel_quickmenu_align > 0)
558         {
559                 float real_desc_width = stringwidth_colors(entry, fontsize);
560                 offset = (desc_width - real_desc_width) * min(autocvar_hud_panel_quickmenu_align, 1);
561
562                 if(option)
563                 {
564                         // when there's enough room align description regardless the checkbox
565                         float extra_offset = (panel_size.x - desc_width) * min(autocvar_hud_panel_quickmenu_align, 1);
566                         if(offset + real_desc_width + extra_offset < desc_width)
567                                 offset += extra_offset;
568                         else
569                                 offset = max(0, desc_width - real_desc_width);
570                 }
571                 drawcolorcodedstring(pos + eX * offset, entry, fontsize, panel_fg_alpha, DRAWFLAG_ADDITIVE);
572         }
573         else
574                 drawcolorcodedstring(pos, entry, fontsize, panel_fg_alpha, DRAWFLAG_ADDITIVE);
575 }
576
577 void HUD_QuickMenu(void)
578 {
579         if(!autocvar__hud_configure)
580         {
581                 if (hud_configure_prev && hud_configure_prev != -1)
582                         QuickMenu_Close();
583
584                 if(!hud_draw_maximized) return;
585                 if(mv_active) return;
586                 //if(!autocvar_hud_panel_quickmenu) return;
587                 if(!hud_panel_quickmenu) return;
588
589                 if(time > QuickMenu_TimeOut)
590                 {
591                         QuickMenu_Close();
592                         return;
593                 }
594         }
595         else
596         {
597                 if(!QuickMenu_IsOpened())
598                 {
599                         QuickMenu_Page_Entries = 1;
600                         QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Submenu%d"), QuickMenu_Page_Entries), "");
601                         ++QuickMenu_Page_Entries;
602                         QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Submenu%d"), QuickMenu_Page_Entries), "");
603                         ++QuickMenu_Page_Entries;
604                         // although real command doesn't matter here, it must not be empty
605                         // otherwise the entry is displayed like a submenu
606                         for (; QuickMenu_Page_Entries < QUICKMENU_MAXLINES - 1; ++QuickMenu_Page_Entries)
607                                 QuickMenu_Page_LoadEntry(QuickMenu_Page_Entries, sprintf(_("Command%d"), QuickMenu_Page_Entries), "-");
608                         ++QuickMenu_Page_Entries;
609                         QuickMenu_Page_ClearEntry(QuickMenu_Page_Entries);
610                         QuickMenu_IsLastPage = false;
611                 }
612         }
613
614         HUD_Panel_UpdateCvars();
615
616         HUD_Panel_DrawBg(1);
617
618         if(panel_bg_padding)
619         {
620                 panel_pos += '1 1 0' * panel_bg_padding;
621                 panel_size -= '2 2 0' * panel_bg_padding;
622         }
623
624         int i;
625         vector fontsize;
626         string color;
627         fontsize = '1 1 0' * (panel_size.y / QUICKMENU_MAXLINES);
628
629         if (!QuickMenu_IsLastPage)
630         {
631                 color = "^5";
632                 HUD_Quickmenu_DrawEntry(panel_pos + eY * (panel_size.y - fontsize.y), sprintf("%d: %s%s", 0, color, _("Continue...")), string_null, fontsize);
633         }
634         else
635                 panel_pos.y += ((QUICKMENU_MAXLINES - QuickMenu_Page_Entries) * fontsize.y) / 2;
636
637         for (i = 1; i <= QuickMenu_Page_Entries; ++i) {
638                 if (QuickMenu_Page_Description[i] == "")
639                         break;
640                 string option = string_null;
641                 if (QuickMenu_Page_Command[i] == "")
642                         color = "^4";
643                 else
644                 {
645                         color = "^3";
646                         if(QuickMenu_Page_Command_Type[i] == 1) // toggle command
647                         {
648                                 int end = strstrofs(QuickMenu_Page_Command[i], ";", 0);
649                                 if(end < 0)
650                                         tokenize_console(QuickMenu_Page_Command[i]);
651                                 else
652                                         tokenize_console(substring(QuickMenu_Page_Command[i], 0, end));
653
654                                 //if(argv(1) && argv(0) == "toggle") // already checked
655                                 {
656                                         // "enable feature xxx" "toggle xxx" (or "toggle xxx 1 0")
657                                         // "disable feature xxx" "toggle xxx 0 1"
658                                         float ON_value = 1, OFF_value = 0;
659                                         if(argv(2))
660                                                 ON_value = stof(argv(2));
661
662                                         if(argv(3))
663                                                 OFF_value = stof(argv(3));
664                                         else
665                                                 OFF_value = !ON_value;
666
667                                         float value = cvar(argv(1));
668                                         if(value == ON_value)
669                                                 option = "checkbox_checked";
670                                         else if(value == OFF_value)
671                                                 option = "checkbox_empty";
672                                         else
673                                                 option = "checkbox_undefined";
674                                 }
675                         }
676                 }
677                 HUD_Quickmenu_DrawEntry(panel_pos, sprintf("%d: %s%s", i, color, QuickMenu_Page_Description[i]), option, fontsize);
678
679                 if(QuickMenu_Page_ActivatedEntry_Time && time < QuickMenu_Page_ActivatedEntry_Time
680                         && QuickMenu_Page_ActivatedEntry == i)
681                         drawfill(panel_pos, eX * panel_size.x + eY * fontsize.y, '0.5 1 0.5', .2, DRAWFLAG_NORMAL);
682
683                 panel_pos.y += fontsize.y;
684         }
685
686         if(QuickMenu_Page_ActivatedEntry >= 0 && time >= QuickMenu_Page_ActivatedEntry_Time)
687         {
688                 if(!QuickMenu_Page_Command[QuickMenu_Page_ActivatedEntry])
689                 {
690                         bool f = QuickMenu_ActionForNumber(QuickMenu_Page_ActivatedEntry);
691                         if(f && QuickMenu_Page_ActivatedEntry_Close)
692                                 QuickMenu_Close();
693                 }
694                 else if(QuickMenu_Page_ActivatedEntry_Close)
695                         QuickMenu_Close();
696                 QuickMenu_Page_ActivatedEntry = -1;
697                 QuickMenu_Page_ActivatedEntry_Time = 0;
698         }
699 }
700
701
702 #define QUICKMENU_SMENU(submenu,eng_submenu) { \
703         if(target_submenu == eng_submenu && target_submenu_found) \
704                 return; /* target_submenu entries are now loaded, exit */ \
705         if(QuickMenu_Buffer_Size < QUICKMENU_BUFFER_MAXENTRIES) \
706                 QuickMenu_Buffer_Set(QM_TAG_SUBMENU, submenu); \
707         ++QuickMenu_Buffer_Size; \
708         if(target_submenu == eng_submenu && !target_submenu_found) { \
709                 QuickMenu_Buffer_Size = 0; /* enable load of next entries */ \
710                 target_submenu_found = true; \
711         } \
712 }
713
714 #define QUICKMENU_ENTRY(title,command) { \
715         if(QuickMenu_Buffer_Size + 1 < QUICKMENU_BUFFER_MAXENTRIES) \
716         { \
717                 QuickMenu_Buffer_Set(QM_TAG_TITLE, title); \
718                 ++QuickMenu_Buffer_Size; \
719                 QuickMenu_Buffer_Set(QM_TAG_COMMAND, command); \
720         } \
721         ++QuickMenu_Buffer_Size; \
722 }
723
724 #define QUICKMENU_SMENU_PL(submenu,eng_submenu,command,teamplayers,without_me) { \
725         if(QuickMenu_Buffer_Size + 3 < QUICKMENU_BUFFER_MAXENTRIES) {\
726                 QUICKMENU_SMENU(submenu,eng_submenu) \
727                 QuickMenu_Buffer_Set(QM_TAG_TITLE, strcat(ftos(teamplayers), ftos(without_me))); \
728                 ++QuickMenu_Buffer_Size; \
729                 QuickMenu_Buffer_Set(QM_TAG_PLCOMMAND, command); \
730                 ++QuickMenu_Buffer_Size; \
731                 QUICKMENU_SMENU(submenu,eng_submenu) \
732         } \
733 }
734
735
736
737 // useful to Translate a string inside the Command
738 #define QUICKMENU_ENTRY_TC(title,command,text,translated_text) {\
739         if(prvm_language == "en") \
740                 QUICKMENU_ENTRY(title, sprintf(command, text)) \
741         else if(!autocvar_hud_panel_quickmenu_translatecommands || translated_text == text) \
742                 QUICKMENU_ENTRY(strcat("(en)", title), sprintf(command, text)) \
743         else \
744                 QUICKMENU_ENTRY(strcat("(", prvm_language, ")", title), sprintf(command, translated_text)) \
745 }
746
747 void HUD_Quickmenu_PlayerListEntries(string cmd, float teamplayers, float without_me)
748 {
749         entity pl;
750         if(teamplayers && !team_count)
751                 return;
752
753         for(pl = players.sort_next; pl; pl = pl.sort_next)
754         {
755                 if(teamplayers == 1 && (pl.team != myteam || pl.team == NUM_SPECTATOR)) // only own team players
756                         continue;
757                 if(teamplayers == 2 && (pl.team == myteam || pl.team == NUM_SPECTATOR)) // only enemy team players
758                         continue;
759                 if(without_me && pl.sv_entnum == player_localnum)
760                         continue;
761                 QUICKMENU_ENTRY(GetPlayerName(pl.sv_entnum), sprintf(cmd, GetPlayerName(pl.sv_entnum)))
762         }
763
764         return;
765 }
766
767
768 // Specifying target_submenu, this function only loads entries inside target_submenu
769 // NOTE: alternatively we could have loaded the whole default quickmenu and
770 // then called QuickMenu_Page_Load(target_submenu, 0);
771 // but this sytem is more reliable since we can always refer to target_submenu
772 // with the English title even if a translation is active
773 void QuickMenu_Default(string target_submenu)
774 {
775         bool target_submenu_found = false;
776         if(target_submenu != "")
777                 QuickMenu_Buffer_Size = QUICKMENU_BUFFER_MAXENTRIES; // forbids load of next entries until target_submenu
778
779         QUICKMENU_SMENU(CTX(_("QMCMD^Chat")), "Chat")
780                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^nice one")), "say %s", ":-) / nice one", CTX(_("QMCMD^:-) / nice one")))
781                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^good game")), "say %s", "good game", CTX(_("QMCMD^good game")))
782                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^hi / good luck")), "say %s", "hi / good luck and have fun", CTX(_("QMCMD^hi / good luck and have fun")))
783         QUICKMENU_SMENU(CTX(_("QMCMD^Chat")), "Chat")
784
785         if(teamplay)
786         {
787         QUICKMENU_SMENU(CTX(_("QMCMD^Team chat")), "Team chat")
788                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^quad soon")), "say_team %s", "quad soon", CTX(_("QMCMD^quad soon")))
789                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^free item, icon")), "say_team %s; g_waypointsprite_team_here_p", "free item %x^7 (l:%y^7)", CTX(_("QMCMD^free item %x^7 (l:%y^7)")))
790                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^took item, icon")), "say_team %s; g_waypointsprite_team_here", "took item (l:%l^7)", CTX(_("QMCMD^took item (l:%l^7)")))
791                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^negative")), "say_team %s", "negative", CTX(_("QMCMD^negative")))
792                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^positive")), "say_team %s", "positive", CTX(_("QMCMD^positive")))
793                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^need help, icon")), "say_team %s; g_waypointsprite_team_helpme; cmd voice needhelp", "need help (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^need help (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)")))
794                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^enemy seen, icon")), "say_team %s; g_waypointsprite_team_danger_p; cmd voice incoming", "enemy seen (l:%y^7)", CTX(_("QMCMD^enemy seen (l:%y^7)")))
795                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^flag seen, icon")), "say_team %s; g_waypointsprite_team_here_p; cmd voice seenflag", "flag seen (l:%y^7)", CTX(_("QMCMD^flag seen (l:%y^7)")))
796                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^defending, icon")), "say_team %s; g_waypointsprite_team_here", "defending (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^defending (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)")))
797                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^roaming, icon")), "say_team %s; g_waypointsprite_team_here", "roaming (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^roaming (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)")))
798                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^attacking, icon")), "say_team %s; g_waypointsprite_team_here", "attacking (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)", CTX(_("QMCMD^attacking (l:%l^7) (h:%h^7 a:%a^7 w:%w^7)")))
799                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^killed flag, icon")), "say_team %s; g_waypointsprite_team_here_p", "killed flagcarrier (l:%y^7)", CTX(_("QMCMD^killed flagcarrier (l:%y^7)")))
800                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^dropped flag, icon")), "say_team %s; g_waypointsprite_team_here_d", "dropped flag (l:%d^7)", CTX(_("QMCMD^dropped flag (l:%d^7)")))
801                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^drop gun, icon")), "say_team %s; g_waypointsprite_team_here; wait; dropweapon", "dropped gun %w^7 (l:%l^7)", CTX(_("QMCMD^dropped gun %w^7 (l:%l^7)")))
802                 QUICKMENU_ENTRY_TC(CTX(_("QMCMD^drop flag/key, icon")), "say_team %s; g_waypointsprite_team_here; wait; +use", "dropped flag/key %w^7 (l:%l^7)", CTX(_("QMCMD^dropped flag/key %w^7 (l:%l^7)")))
803         QUICKMENU_SMENU(CTX(_("QMCMD^Team chat")), "Team chat")
804         }
805
806         QUICKMENU_SMENU_PL(CTX(_("QMCMD^Send private message to")), "Send private message to", "commandmode tell \"%s^7\"", 0, 1)
807
808         QUICKMENU_SMENU(CTX(_("QMCMD^Settings")), "Settings")
809                 QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings")), "View/HUD settings")
810                         QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person view")), "toggle chase_active")
811                         QUICKMENU_ENTRY(CTX(_("QMCMD^Player models like mine")), "toggle cl_forceplayermodels")
812                         QUICKMENU_ENTRY(CTX(_("QMCMD^Names above players")), "toggle hud_shownames")
813                         QUICKMENU_ENTRY(CTX(_("QMCMD^Crosshair per weapon")), "toggle crosshair_per_weapon")
814                         QUICKMENU_ENTRY(CTX(_("QMCMD^FPS")), "toggle hud_panel_engineinfo")
815                         QUICKMENU_ENTRY(CTX(_("QMCMD^Net graph")), "toggle shownetgraph")
816                 QUICKMENU_SMENU(CTX(_("QMCMD^View/HUD settings")), "View/HUD settings")
817
818                 QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings")), "Sound settings")
819                         QUICKMENU_ENTRY(CTX(_("QMCMD^Hit sound")), "toggle cl_hitsound")
820                         QUICKMENU_ENTRY(CTX(_("QMCMD^Chat sound")), "toggle con_chatsound")
821                 QUICKMENU_SMENU(CTX(_("QMCMD^Sound settings")), "Sound settings")
822
823                 if(spectatee_status > 0)
824                 {
825                 QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera")), "Spectator camera")
826                         QUICKMENU_ENTRY(CTX(_("QMCMD^1st person")), "chase_active 0; -use")
827                         QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person around player")), "chase_active 1; +use")
828                         QUICKMENU_ENTRY(CTX(_("QMCMD^3rd person behind")), "chase_active 1; -use")
829                 QUICKMENU_SMENU(CTX(_("QMCMD^Spectator camera")), "Spectator camera")
830                 }
831
832                 if(spectatee_status == -1)
833                 {
834                 QUICKMENU_SMENU(CTX(_("QMCMD^Observer camera")), "Observer camera")
835                         QUICKMENU_ENTRY(CTX(_("QMCMD^Increase speed")), "weapnext")
836                         QUICKMENU_ENTRY(CTX(_("QMCMD^Decrease speed")), "weapprev")
837                         QUICKMENU_ENTRY(CTX(_("QMCMD^Wall collision off")), "+use")
838                         QUICKMENU_ENTRY(CTX(_("QMCMD^Wall collision on")), "-use")
839                 QUICKMENU_SMENU(CTX(_("QMCMD^Observer camera")), "Observer camera")
840                 }
841
842                 QUICKMENU_ENTRY(CTX(_("QMCMD^Fullscreen")), "toggle vid_fullscreen; vid_restart")
843                 if(prvm_language != "en")
844                 QUICKMENU_ENTRY(CTX(_("QMCMD^Translate chat messages")), "toggle hud_panel_quickmenu_translatecommands")
845         QUICKMENU_SMENU(CTX(_("QMCMD^Settings")), "Settings")
846
847         QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote")), "Call a vote")
848                 QUICKMENU_ENTRY(CTX(_("QMCMD^Restart the map")), "vcall restart")
849                 QUICKMENU_ENTRY(CTX(_("QMCMD^End match")), "vcall endmatch")
850                 if(getstatf(STAT_TIMELIMIT) > 0)
851                 {
852                 QUICKMENU_ENTRY(CTX(_("QMCMD^Reduce match time")), "vcall reducematchtime")
853                 QUICKMENU_ENTRY(CTX(_("QMCMD^Extend match time")), "vcall extendmatchtime")
854                 }
855                 if(teamplay)
856                 QUICKMENU_ENTRY(CTX(_("QMCMD^Shuffle teams")), "vcall shuffleteams")
857         QUICKMENU_SMENU(CTX(_("QMCMD^Call a vote")), "Call a vote")
858
859         if(target_submenu != "" && !target_submenu_found)
860         {
861                 printf("Couldn't find submenu \"%s\"\n", target_submenu);
862                 if(prvm_language != "en")
863                         printf("^3Warning: submenu must be in English\n", target_submenu);
864                 QuickMenu_Buffer_Size = 0;
865         }
866 }
867 #undef QUICKMENU_SMENU
868 #undef QUICKMENU_ENTRY
869 #undef QUICKMENU_ENTRY_TC