]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud.qc
Properly hide minigame panels from the hud editor
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud.qc
1 #include "hud.qh"
2 #include "_all.qh"
3
4 #include "hud_config.qh"
5 #include "mapvoting.qh"
6 #include "scoreboard.qh"
7 #include "teamradar.qh"
8 #include "t_items.qh"
9
10 #include "../dpdefs/keycodes.qh"
11
12 #include "../common/buffs/all.qh"
13 #include "../common/constants.qh"
14 #include "../common/deathtypes.qh"
15 #include "../common/items/all.qc"
16 #include "../common/mapinfo.qh"
17 #include "../common/nades/all.qh"
18
19 #include "../common/mutators/mutator/waypoints/all.qh"
20 #include "../server/mutators/gamemode_ctf.qh"
21
22 #include "../common/stats.qh"
23
24 #include "../csqcmodellib/cl_player.qh"
25
26
27 /*
28 ==================
29 Misc HUD functions
30 ==================
31 */
32
33 vector HUD_Get_Num_Color (float x, float maxvalue)
34 {
35         float blinkingamt;
36         vector color;
37         if(x >= maxvalue) {
38                 color.x = sin(2*M_PI*time);
39                 color.y = 1;
40                 color.z = sin(2*M_PI*time);
41         }
42         else if(x > maxvalue * 0.75) {
43                 color.x = 0.4 - (x-150)*0.02 * 0.4; //red value between 0.4 -> 0
44                 color.y = 0.9 + (x-150)*0.02 * 0.1; // green value between 0.9 -> 1
45                 color.z = 0;
46         }
47         else if(x > maxvalue * 0.5) {
48                 color.x = 1 - (x-100)*0.02 * 0.6; //red value between 1 -> 0.4
49                 color.y = 1 - (x-100)*0.02 * 0.1; // green value between 1 -> 0.9
50                 color.z = 1 - (x-100)*0.02; // blue value between 1 -> 0
51         }
52         else if(x > maxvalue * 0.25) {
53                 color.x = 1;
54                 color.y = 1;
55                 color.z = 0.2 + (x-50)*0.02 * 0.8; // blue value between 0.2 -> 1
56         }
57         else if(x > maxvalue * 0.1) {
58                 color.x = 1;
59                 color.y = (x-20)*90/27/100; // green value between 0 -> 1
60                 color.z = (x-20)*90/27/100 * 0.2; // blue value between 0 -> 0.2
61         }
62         else {
63                 color.x = 1;
64                 color.y = 0;
65                 color.z = 0;
66         }
67
68         blinkingamt = (1 - x/maxvalue/0.25);
69         if(blinkingamt > 0)
70         {
71                 color.x = color.x - color.x * blinkingamt * sin(2*M_PI*time);
72                 color.y = color.y - color.y * blinkingamt * sin(2*M_PI*time);
73                 color.z = color.z - color.z * blinkingamt * sin(2*M_PI*time);
74         }
75         return color;
76 }
77
78 float HUD_GetRowCount(int item_count, vector size, float item_aspect)
79 {
80         float aspect = size_y / size_x;
81         return bound(1, floor((sqrt(4 * item_aspect * aspect * item_count + aspect * aspect) + aspect + 0.5) / 2), item_count);
82 }
83
84 vector HUD_GetTableSize_BestItemAR(int item_count, vector psize, float item_aspect)
85 {
86         float columns, rows;
87         float ratio, best_ratio = 0;
88         float best_columns = 1, best_rows = 1;
89         bool vertical = (psize.x / psize.y >= item_aspect);
90         if(vertical)
91         {
92                 psize = eX * psize.y + eY * psize.x;
93                 item_aspect = 1 / item_aspect;
94         }
95
96         rows = ceil(sqrt(item_count));
97         columns = ceil(item_count/rows);
98         while(columns >= 1)
99         {
100                 ratio = (psize.x/columns) / (psize.y/rows);
101                 if(ratio > item_aspect)
102                         ratio = item_aspect * item_aspect / ratio;
103
104                 if(ratio <= best_ratio)
105                         break; // ratio starts decreasing by now, skip next configurations
106
107                 best_columns = columns;
108                 best_rows = rows;
109                 best_ratio = ratio;
110
111                 if(columns == 1)
112                         break;
113
114                 --columns;
115                 rows = ceil(item_count/columns);
116         }
117
118         if(vertical)
119                 return eX * best_rows + eY * best_columns;
120         else
121                 return eX * best_columns + eY * best_rows;
122 }
123
124 // return the string of the onscreen race timer
125 string MakeRaceString(int cp, float mytime, float theirtime, float lapdelta, string theirname)
126 {
127         string col;
128         string timestr;
129         string cpname;
130         string lapstr;
131         lapstr = "";
132
133         if(theirtime == 0) // goal hit
134         {
135                 if(mytime > 0)
136                 {
137                         timestr = strcat("+", ftos_decimals(+mytime, TIME_DECIMALS));
138                         col = "^1";
139                 }
140                 else if(mytime == 0)
141                 {
142                         timestr = "+0.0";
143                         col = "^3";
144                 }
145                 else
146                 {
147                         timestr = strcat("-", ftos_decimals(-mytime, TIME_DECIMALS));
148                         col = "^2";
149                 }
150
151                 if(lapdelta > 0)
152                 {
153                         lapstr = sprintf(_(" (-%dL)"), lapdelta);
154                         col = "^2";
155                 }
156                 else if(lapdelta < 0)
157                 {
158                         lapstr = sprintf(_(" (+%dL)"), -lapdelta);
159                         col = "^1";
160                 }
161         }
162         else if(theirtime > 0) // anticipation
163         {
164                 if(mytime >= theirtime)
165                         timestr = strcat("+", ftos_decimals(mytime - theirtime, TIME_DECIMALS));
166                 else
167                         timestr = TIME_ENCODED_TOSTRING(TIME_ENCODE(theirtime));
168                 col = "^3";
169         }
170         else
171         {
172                 col = "^7";
173                 timestr = "";
174         }
175
176         if(cp == 254)
177                 cpname = _("Start line");
178         else if(cp == 255)
179                 cpname = _("Finish line");
180         else if(cp)
181                 cpname = sprintf(_("Intermediate %d"), cp);
182         else
183                 cpname = _("Finish line");
184
185         if(theirtime < 0)
186                 return strcat(col, cpname);
187         else if(theirname == "")
188                 return strcat(col, sprintf("%s (%s)", cpname, timestr));
189         else
190                 return strcat(col, sprintf("%s (%s %s)", cpname, timestr, strcat(theirname, col, lapstr)));
191 }
192
193 // Check if the given name already exist in race rankings? In that case, where? (otherwise return 0)
194 int race_CheckName(string net_name)
195 {
196         int i;
197         for (i=RANKINGS_CNT-1;i>=0;--i)
198                 if(grecordholder[i] == net_name)
199                         return i+1;
200         return 0;
201 }
202
203 /*
204 ==================
205 HUD panels
206 ==================
207 */
208
209 // draw the background/borders
210 #define HUD_Panel_DrawBg(theAlpha) do {                                                                                                                                                         \
211         if(panel.current_panel_bg != "0" && panel.current_panel_bg != "")                                                                                               \
212                 draw_BorderPicture(panel_pos - '1 1 0' * panel_bg_border, panel.current_panel_bg, panel_size + '1 1 0' * 2 * panel_bg_border, panel_bg_color, panel_bg_alpha * theAlpha, '1 1 0' * (panel_bg_border/BORDER_MULTIPLIER));\
213 } while(0)
214
215 //basically the same code of draw_ButtonPicture and draw_VertButtonPicture for the menu
216 void HUD_Panel_DrawProgressBar(vector theOrigin, vector theSize, string pic, float length_ratio, bool vertical, float baralign, vector theColor, float theAlpha, int drawflag)
217 {
218         if(!length_ratio || !theAlpha)
219                 return;
220         if(length_ratio > 1)
221                 length_ratio = 1;
222         if (baralign == 3)
223         {
224                 if(length_ratio < -1)
225                         length_ratio = -1;
226         }
227         else if(length_ratio < 0)
228                 return;
229
230         vector square;
231         vector width, height;
232         if(vertical) {
233                 pic = strcat(hud_skin_path, "/", pic, "_vertical");
234                 if(precache_pic(pic) == "") {
235                         pic = "gfx/hud/default/progressbar_vertical";
236                 }
237
238         if (baralign == 1) // bottom align
239                         theOrigin.y += (1 - length_ratio) * theSize.y;
240         else if (baralign == 2) // center align
241             theOrigin.y += 0.5 * (1 - length_ratio) * theSize.y;
242         else if (baralign == 3) // center align, positive values down, negative up
243                 {
244                         theSize.y *= 0.5;
245                         if (length_ratio > 0)
246                                 theOrigin.y += theSize.y;
247                         else
248                         {
249                                 theOrigin.y += (1 + length_ratio) * theSize.y;
250                                 length_ratio = -length_ratio;
251                         }
252                 }
253                 theSize.y *= length_ratio;
254
255                 vector bH;
256                 width = eX * theSize.x;
257                 height = eY * theSize.y;
258                 if(theSize.y <= theSize.x * 2)
259                 {
260                         // button not high enough
261                         // draw just upper and lower part then
262                         square = eY * theSize.y * 0.5;
263                         bH = eY * (0.25 * theSize.y / (theSize.x * 2));
264                         drawsubpic(theOrigin,          square + width, pic, '0 0 0', eX + bH, theColor, theAlpha, drawflag);
265                         drawsubpic(theOrigin + square, square + width, pic, eY - bH, eX + bH, theColor, theAlpha, drawflag);
266                 }
267                 else
268                 {
269                         square = eY * theSize.x;
270                         drawsubpic(theOrigin,                   width   +     square, pic, '0 0    0', '1 0.25 0', theColor, theAlpha, drawflag);
271                         drawsubpic(theOrigin +          square, theSize - 2 * square, pic, '0 0.25 0', '1 0.5  0', theColor, theAlpha, drawflag);
272                         drawsubpic(theOrigin + height - square, width   +     square, pic, '0 0.75 0', '1 0.25 0', theColor, theAlpha, drawflag);
273                 }
274         } else {
275                 pic = strcat(hud_skin_path, "/", pic);
276                 if(precache_pic(pic) == "") {
277                         pic = "gfx/hud/default/progressbar";
278                 }
279
280                 if (baralign == 1) // right align
281                         theOrigin.x += (1 - length_ratio) * theSize.x;
282         else if (baralign == 2) // center align
283             theOrigin.x += 0.5 * (1 - length_ratio) * theSize.x;
284         else if (baralign == 3) // center align, positive values on the right, negative on the left
285                 {
286                         theSize.x *= 0.5;
287                         if (length_ratio > 0)
288                                 theOrigin.x += theSize.x;
289                         else
290                         {
291                                 theOrigin.x += (1 + length_ratio) * theSize.x;
292                                 length_ratio = -length_ratio;
293                         }
294                 }
295                 theSize.x *= length_ratio;
296
297                 vector bW;
298                 width = eX * theSize.x;
299                 height = eY * theSize.y;
300                 if(theSize.x <= theSize.y * 2)
301                 {
302                         // button not wide enough
303                         // draw just left and right part then
304                         square = eX * theSize.x * 0.5;
305                         bW = eX * (0.25 * theSize.x / (theSize.y * 2));
306                         drawsubpic(theOrigin,          square + height, pic, '0 0 0', eY + bW, theColor, theAlpha, drawflag);
307                         drawsubpic(theOrigin + square, square + height, pic, eX - bW, eY + bW, theColor, theAlpha, drawflag);
308                 }
309                 else
310                 {
311                         square = eX * theSize.y;
312                         drawsubpic(theOrigin,                  height  +     square, pic, '0    0 0', '0.25 1 0', theColor, theAlpha, drawflag);
313                         drawsubpic(theOrigin +         square, theSize - 2 * square, pic, '0.25 0 0', '0.5  1 0', theColor, theAlpha, drawflag);
314                         drawsubpic(theOrigin + width - square, height  +     square, pic, '0.75 0 0', '0.25 1 0', theColor, theAlpha, drawflag);
315                 }
316         }
317 }
318
319 void HUD_Panel_DrawHighlight(vector pos, vector mySize, vector color, float theAlpha, int drawflag)
320 {
321         if(!theAlpha)
322                 return;
323
324         string pic;
325         pic = strcat(hud_skin_path, "/num_leading");
326         if(precache_pic(pic) == "") {
327                 pic = "gfx/hud/default/num_leading";
328         }
329
330         drawsubpic(pos, eX * min(mySize.x * 0.5, mySize.y) + eY * mySize.y, pic, '0 0 0', '0.25 1 0', color, theAlpha, drawflag);
331         if(mySize.x/mySize.y > 2)
332                 drawsubpic(pos + eX * mySize.y, eX * (mySize.x - 2 * mySize.y) + eY * mySize.y, pic, '0.25 0 0', '0.5 1 0', color, theAlpha, drawflag);
333         drawsubpic(pos + eX * mySize.x - eX * min(mySize.x * 0.5, mySize.y), eX * min(mySize.x * 0.5, mySize.y) + eY * mySize.y, pic, '0.75 0 0', '0.25 1 0', color, theAlpha, drawflag);
334 }
335
336 // Weapon icons (#0)
337 //
338 entity weaponorder[Weapons_MAX];
339 void weaponorder_swap(int i, int j, entity pass)
340 {
341         entity h = weaponorder[i];
342         weaponorder[i] = weaponorder[j];
343         weaponorder[j] = h;
344 }
345
346 string weaponorder_cmp_str;
347 int weaponorder_cmp(int i, int j, entity pass)
348 {
349         int ai, aj;
350         ai = strstrofs(weaponorder_cmp_str, sprintf(" %d ", weaponorder[i].weapon), 0);
351         aj = strstrofs(weaponorder_cmp_str, sprintf(" %d ", weaponorder[j].weapon), 0);
352         return aj - ai; // the string is in REVERSE order (higher prio at the right is what we want, but higher prio first is the string)
353 }
354
355 void HUD_Weapons(void)
356 {SELFPARAM();
357         // declarations
358         WepSet weapons_stat = WepSet_GetFromStat();
359         int i;
360         float f, a;
361         float screen_ar;
362         vector center = '0 0 0';
363         int weapon_count, weapon_id;
364         int row, column, rows = 0, columns = 0;
365         bool vertical_order = true;
366         float aspect = autocvar_hud_panel_weapons_aspect;
367
368         float timeout = autocvar_hud_panel_weapons_timeout;
369         float timein_effect_length = autocvar_hud_panel_weapons_timeout_speed_in; //? 0.375 : 0);
370         float timeout_effect_length = autocvar_hud_panel_weapons_timeout_speed_out; //? 0.75 : 0);
371
372         vector barsize = '0 0 0', baroffset = '0 0 0';
373         vector ammo_color = '1 0 1';
374         float ammo_alpha = 1;
375
376         float when = max(1, autocvar_hud_panel_weapons_complainbubble_time);
377         float fadetime = max(0, autocvar_hud_panel_weapons_complainbubble_fadetime);
378
379         vector weapon_pos, weapon_size = '0 0 0';
380         vector color;
381
382         // check to see if we want to continue
383         if(hud != HUD_NORMAL) return;
384
385         if(!autocvar__hud_configure)
386         {
387                 if((!autocvar_hud_panel_weapons) || (spectatee_status == -1))
388                         return;
389                 if(timeout && time >= weapontime + timeout + timeout_effect_length)
390                 if(autocvar_hud_panel_weapons_timeout_effect == 3 || (autocvar_hud_panel_weapons_timeout_effect == 1 && !(autocvar_hud_panel_weapons_timeout_fadebgmin + autocvar_hud_panel_weapons_timeout_fadefgmin)))
391                 {
392                         weaponprevtime = time;
393                         return;
394                 }
395         }
396
397         // update generic hud functions
398         HUD_Panel_UpdateCvars();
399
400         // figure out weapon order (how the weapons are sorted) // TODO make this configurable
401         if(weaponorder_bypriority != autocvar_cl_weaponpriority || !weaponorder[0])
402         {
403                 int weapon_cnt;
404                 if(weaponorder_bypriority)
405                         strunzone(weaponorder_bypriority);
406                 if(weaponorder_byimpulse)
407                         strunzone(weaponorder_byimpulse);
408
409                 weaponorder_bypriority = strzone(autocvar_cl_weaponpriority);
410                 weaponorder_byimpulse = strzone(W_FixWeaponOrder_BuildImpulseList(W_FixWeaponOrder_ForceComplete(W_NumberWeaponOrder(weaponorder_bypriority))));
411                 weaponorder_cmp_str = strcat(" ", weaponorder_byimpulse, " ");
412
413                 weapon_cnt = 0;
414                 for(i = WEP_FIRST; i <= WEP_LAST; ++i)
415                 {
416                         setself(get_weaponinfo(i));
417                         if(self.impulse >= 0)
418                         {
419                                 weaponorder[weapon_cnt] = self;
420                                 ++weapon_cnt;
421                         }
422                 }
423                 for(i = weapon_cnt; i < Weapons_MAX; ++i)
424                         weaponorder[i] = world;
425                 heapsort(weapon_cnt, weaponorder_swap, weaponorder_cmp, world);
426
427                 weaponorder_cmp_str = string_null;
428         }
429
430         if(!autocvar_hud_panel_weapons_complainbubble || autocvar__hud_configure || time - complain_weapon_time >= when + fadetime)
431                 complain_weapon = 0;
432
433         if(autocvar__hud_configure)
434         {
435                 if(!weapons_stat)
436                         for(i = WEP_FIRST; i <= WEP_LAST; i += floor((WEP_LAST-WEP_FIRST)/5))
437                                 weapons_stat |= WepSet_FromWeapon(i);
438
439                 #if 0
440                 /// debug code
441                 if(cvar("wep_add"))
442                 {
443                         weapons_stat = '0 0 0';
444                         float countw = 1 + floor((floor(time * cvar("wep_add"))) % (Weapons_COUNT - 1));
445                         for(i = WEP_FIRST; i <= countw; ++i)
446                                 weapons_stat |= WepSet_FromWeapon(i);
447                 }
448                 #endif
449         }
450
451         // determine which weapons are going to be shown
452         if (autocvar_hud_panel_weapons_onlyowned)
453         {
454                 if(autocvar__hud_configure)
455                 {
456                         if(menu_enabled != 2)
457                                 HUD_Panel_DrawBg(1); // also draw the bg of the entire panel
458                 }
459
460                 // do we own this weapon?
461                 weapon_count = 0;
462                 for(i = 0; i <= WEP_LAST-WEP_FIRST; ++i)
463                         if((weapons_stat & WepSet_FromWeapon(weaponorder[i].weapon)) || (weaponorder[i].weapon == complain_weapon))
464                                 ++weapon_count;
465
466
467                 // might as well commit suicide now, no reason to live ;)
468                 if (weapon_count == 0)
469                         return;
470
471                 vector old_panel_size = panel_size;
472                 vector padded_panel_size = panel_size - '2 2 0' * panel_bg_padding;
473
474                 // get the all-weapons layout
475                 int nHidden = 0;
476                 WepSet weapons_stat = WepSet_GetFromStat();
477                 for (int i = WEP_FIRST; i <= WEP_LAST; ++i) {
478                         WepSet weapons_wep = WepSet_FromWeapon(i);
479                         if (weapons_stat & weapons_wep) continue;
480                         Weapon w = get_weaponinfo(i);
481                         if (w.spawnflags & WEP_FLAG_MUTATORBLOCKED) nHidden += 1;
482                 }
483                 vector table_size = HUD_GetTableSize_BestItemAR((Weapons_COUNT - 1) - nHidden, padded_panel_size, aspect);
484                 columns = table_size.x;
485                 rows = table_size.y;
486                 weapon_size.x = padded_panel_size.x / columns;
487                 weapon_size.y = padded_panel_size.y / rows;
488
489                 // NOTE: although weapons should aways look the same even if onlyowned is enabled,
490                 // we enlarge them a bit when possible to better match the desired aspect ratio
491                 if(padded_panel_size.x / padded_panel_size.y < aspect)
492                 {
493                         // maximum number of rows that allows to display items with the desired aspect ratio
494                         int max_rows = floor(padded_panel_size.y / (weapon_size.x / aspect));
495                         columns = min(columns, ceil(weapon_count / max_rows));
496                         rows = ceil(weapon_count / columns);
497                         weapon_size.y = min(padded_panel_size.y / rows, weapon_size.x / aspect);
498                         weapon_size.x = min(padded_panel_size.x / columns, aspect * weapon_size.y);
499                         vertical_order = false;
500                 }
501                 else
502                 {
503                         int max_columns = floor(padded_panel_size.x / (weapon_size.y * aspect));
504                         rows = min(rows, ceil(weapon_count / max_columns));
505                         columns = ceil(weapon_count / rows);
506                         weapon_size.x = min(padded_panel_size.x / columns, aspect * weapon_size.y);
507                         weapon_size.y = min(padded_panel_size.y / rows, weapon_size.x / aspect);
508                         vertical_order = true;
509                 }
510
511                 // reduce size of the panel
512                 panel_size.x = columns * weapon_size.x;
513                 panel_size.y = rows * weapon_size.y;
514                 panel_size += '2 2 0' * panel_bg_padding;
515
516                 // center the resized panel, or snap it to the screen edge when close enough
517                 if(panel_pos.x > vid_conwidth * 0.001)
518                 {
519                         if(panel_pos.x + old_panel_size.x > vid_conwidth * 0.999)
520                                 panel_pos.x += old_panel_size.x - panel_size.x;
521                         else
522                                 panel_pos.x += (old_panel_size.x - panel_size.x) / 2;
523                 }
524                 else if(old_panel_size.x > vid_conwidth * 0.999)
525                         panel_pos.x += (old_panel_size.x - panel_size.x) / 2;
526
527                 if(panel_pos.y > vid_conheight * 0.001)
528                 {
529                         if(panel_pos.y + old_panel_size.y > vid_conheight * 0.999)
530                                 panel_pos.y += old_panel_size.y - panel_size.y;
531                         else
532                                 panel_pos.y += (old_panel_size.y - panel_size.y) / 2;
533                 }
534                 else if(old_panel_size.y > vid_conheight * 0.999)
535                         panel_pos.y += (old_panel_size.y - panel_size.y) / 2;
536         }
537         else
538                 weapon_count = (Weapons_COUNT - 1);
539
540         // animation for fading in/out the panel respectively when not in use
541         if(!autocvar__hud_configure)
542         {
543                 if (timeout && time >= weapontime + timeout) // apply timeout effect if needed
544                 {
545                         f = bound(0, (time - (weapontime + timeout)) / timeout_effect_length, 1);
546
547                         // fade the panel alpha
548                         if(autocvar_hud_panel_weapons_timeout_effect == 1)
549                         {
550                                 panel_bg_alpha *= (autocvar_hud_panel_weapons_timeout_fadebgmin * f + (1 - f));
551                                 panel_fg_alpha *= (autocvar_hud_panel_weapons_timeout_fadefgmin * f + (1 - f));
552                         }
553                         else if(autocvar_hud_panel_weapons_timeout_effect == 3)
554                         {
555                                 panel_bg_alpha *= (1 - f);
556                                 panel_fg_alpha *= (1 - f);
557                         }
558
559                         // move the panel off the screen
560                         if (autocvar_hud_panel_weapons_timeout_effect == 2 || autocvar_hud_panel_weapons_timeout_effect == 3)
561                         {
562                                 f *= f; // for a cooler movement
563                                 center.x = panel_pos.x + panel_size.x/2;
564                                 center.y = panel_pos.y + panel_size.y/2;
565                                 screen_ar = vid_conwidth/vid_conheight;
566                                 if (center.x/center.y < screen_ar) //bottom left
567                                 {
568                                         if ((vid_conwidth - center.x)/center.y < screen_ar) //bottom
569                                                 panel_pos.y += f * (vid_conheight - panel_pos.y);
570                                         else //left
571                                                 panel_pos.x -= f * (panel_pos.x + panel_size.x);
572                                 }
573                                 else //top right
574                                 {
575                                         if ((vid_conwidth - center.x)/center.y < screen_ar) //right
576                                                 panel_pos.x += f * (vid_conwidth - panel_pos.x);
577                                         else //top
578                                                 panel_pos.y -= f * (panel_pos.y + panel_size.y);
579                                 }
580                                 if(f == 1)
581                                         center.x = -1; // mark the panel as off screen
582                         }
583                         weaponprevtime = time - (1 - f) * timein_effect_length;
584                 }
585                 else if (timeout && time < weaponprevtime + timein_effect_length) // apply timein effect if needed
586                 {
587                         f = bound(0, (time - weaponprevtime) / timein_effect_length, 1);
588
589                         // fade the panel alpha
590                         if(autocvar_hud_panel_weapons_timeout_effect == 1)
591                         {
592                                 panel_bg_alpha *= (autocvar_hud_panel_weapons_timeout_fadebgmin * (1 - f) + f);
593                                 panel_fg_alpha *= (autocvar_hud_panel_weapons_timeout_fadefgmin * (1 - f) + f);
594                         }
595                         else if(autocvar_hud_panel_weapons_timeout_effect == 3)
596                         {
597                                 panel_bg_alpha *= (f);
598                                 panel_fg_alpha *= (f);
599                         }
600
601                         // move the panel back on screen
602                         if (autocvar_hud_panel_weapons_timeout_effect == 2 || autocvar_hud_panel_weapons_timeout_effect == 3)
603                         {
604                                 f *= f; // for a cooler movement
605                                 f = 1 - f;
606                                 center.x = panel_pos.x + panel_size.x/2;
607                                 center.y = panel_pos.y + panel_size.y/2;
608                                 screen_ar = vid_conwidth/vid_conheight;
609                                 if (center.x/center.y < screen_ar) //bottom left
610                                 {
611                                         if ((vid_conwidth - center.x)/center.y < screen_ar) //bottom
612                                                 panel_pos.y += f * (vid_conheight - panel_pos.y);
613                                         else //left
614                                                 panel_pos.x -= f * (panel_pos.x + panel_size.x);
615                                 }
616                                 else //top right
617                                 {
618                                         if ((vid_conwidth - center.x)/center.y < screen_ar) //right
619                                                 panel_pos.x += f * (vid_conwidth - panel_pos.x);
620                                         else //top
621                                                 panel_pos.y -= f * (panel_pos.y + panel_size.y);
622                                 }
623                         }
624                 }
625         }
626
627         // draw the background, then change the virtual size of it to better fit other items inside
628         HUD_Panel_DrawBg(1);
629
630         if(center.x == -1)
631                 return;
632
633         if(panel_bg_padding)
634         {
635                 panel_pos += '1 1 0' * panel_bg_padding;
636                 panel_size -= '2 2 0' * panel_bg_padding;
637         }
638
639         // after the sizing and animations are done, update the other values
640
641         if(!rows) // if rows is > 0 onlyowned code has already updated these vars
642         {
643                 vector table_size = HUD_GetTableSize_BestItemAR((Weapons_COUNT - 1), panel_size, aspect);
644                 columns = table_size.x;
645                 rows = table_size.y;
646                 weapon_size.x = panel_size.x / columns;
647                 weapon_size.y = panel_size.y / rows;
648                 vertical_order = (panel_size.x / panel_size.y >= aspect);
649         }
650
651         // calculate position/size for visual bar displaying ammount of ammo status
652         if (autocvar_hud_panel_weapons_ammo)
653         {
654                 ammo_color = stov(autocvar_hud_panel_weapons_ammo_color);
655                 ammo_alpha = panel_fg_alpha * autocvar_hud_panel_weapons_ammo_alpha;
656
657                 if(weapon_size.x/weapon_size.y > aspect)
658                 {
659                         barsize.x = aspect * weapon_size.y;
660                         barsize.y = weapon_size.y;
661                         baroffset.x = (weapon_size.x - barsize.x) / 2;
662                 }
663                 else
664                 {
665                         barsize.y = 1/aspect * weapon_size.x;
666                         barsize.x = weapon_size.x;
667                         baroffset.y = (weapon_size.y - barsize.y) / 2;
668                 }
669         }
670         if(autocvar_hud_panel_weapons_accuracy)
671                 Accuracy_LoadColors();
672
673         // draw items
674         row = column = 0;
675         vector label_size = '1 1 0' * min(weapon_size.x, weapon_size.y) * bound(0, autocvar_hud_panel_weapons_label_scale, 1);
676         vector noncurrent_pos = '0 0 0';
677         vector noncurrent_size = weapon_size * bound(0, autocvar_hud_panel_weapons_noncurrent_scale, 1);
678         float noncurrent_alpha = panel_fg_alpha * bound(0, autocvar_hud_panel_weapons_noncurrent_alpha, 1);
679         bool isCurrent;
680
681         for(i = 0; i <= WEP_LAST-WEP_FIRST; ++i)
682         {
683                 // retrieve information about the current weapon to be drawn
684                 setself(weaponorder[i]);
685                 weapon_id = self.impulse;
686                 isCurrent = (self.weapon == switchweapon);
687
688                 // skip if this weapon doesn't exist
689                 if(!self || weapon_id < 0) { continue; }
690
691                 // skip this weapon if we don't own it (and onlyowned is enabled)-- or if weapons_complainbubble is showing for this weapon
692                 if(autocvar_hud_panel_weapons_onlyowned)
693                 if (!((weapons_stat & WepSet_FromWeapon(self.weapon)) || (self.weapon == complain_weapon)))
694                         continue;
695
696                 // figure out the drawing position of weapon
697                 weapon_pos = (panel_pos + eX * column * weapon_size.x + eY * row * weapon_size.y);
698                 noncurrent_pos.x = weapon_pos.x + (weapon_size.x - noncurrent_size.x) / 2;
699                 noncurrent_pos.y = weapon_pos.y + (weapon_size.y - noncurrent_size.y) / 2;
700
701                 // draw background behind currently selected weapon
702                 if(isCurrent)
703                         drawpic_aspect_skin(weapon_pos, "weapon_current_bg", weapon_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
704
705                 // draw the weapon accuracy
706                 if(autocvar_hud_panel_weapons_accuracy)
707                 {
708                         float panel_weapon_accuracy = weapon_accuracy[self.weapon-WEP_FIRST];
709                         if(panel_weapon_accuracy >= 0)
710                         {
711                                 color = Accuracy_GetColor(panel_weapon_accuracy);
712                                 drawpic_aspect_skin(weapon_pos, "weapon_accuracy", weapon_size, color, panel_fg_alpha, DRAWFLAG_NORMAL);
713                         }
714                 }
715
716                 // drawing all the weapon items
717                 if(weapons_stat & WepSet_FromWeapon(self.weapon))
718                 {
719                         // draw the weapon image
720                         if(isCurrent)
721                                 drawpic_aspect_skin(weapon_pos, self.model2, weapon_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
722                         else
723                                 drawpic_aspect_skin(noncurrent_pos, self.model2, noncurrent_size, '1 1 1', noncurrent_alpha, DRAWFLAG_NORMAL);
724
725                         // draw weapon label string
726                         switch(autocvar_hud_panel_weapons_label)
727                         {
728                                 case 1: // weapon number
729                                         drawstring(weapon_pos, ftos(weapon_id), label_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
730                                         break;
731
732                                 case 2: // bind
733                                         drawstring(weapon_pos, getcommandkey(ftos(weapon_id), strcat("weapon_group_", ftos(weapon_id))), label_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
734                                         break;
735
736                                 case 3: // weapon name
737                                         drawstring(weapon_pos, strtolower(self.message), label_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
738                                         break;
739
740                                 default: // nothing
741                                         break;
742                         }
743
744                         // draw ammo status bar
745                         if(autocvar_hud_panel_weapons_ammo && (self.ammo_field != ammo_none))
746                         {
747                                 float ammo_full;
748                                 a = getstati(GetAmmoStat(self.ammo_field)); // how much ammo do we have?
749
750                                 if(a > 0)
751                                 {
752                                         switch(self.ammo_field)
753                                         {
754                                                 case ammo_shells:  ammo_full = autocvar_hud_panel_weapons_ammo_full_shells;  break;
755                                                 case ammo_nails:   ammo_full = autocvar_hud_panel_weapons_ammo_full_nails;   break;
756                                                 case ammo_rockets: ammo_full = autocvar_hud_panel_weapons_ammo_full_rockets; break;
757                                                 case ammo_cells:   ammo_full = autocvar_hud_panel_weapons_ammo_full_cells;   break;
758                                                 case ammo_plasma:  ammo_full = autocvar_hud_panel_weapons_ammo_full_plasma;  break;
759                                                 case ammo_fuel:    ammo_full = autocvar_hud_panel_weapons_ammo_full_fuel;    break;
760                                                 default: ammo_full = 60;
761                                         }
762
763                                         drawsetcliparea(
764                                                 weapon_pos.x + baroffset.x,
765                                                 weapon_pos.y + baroffset.y,
766                                                 barsize.x * bound(0, a/ammo_full, 1),
767                                                 barsize.y
768                                         );
769
770                                         drawpic_aspect_skin(
771                                                 weapon_pos,
772                                                 "weapon_ammo",
773                                                 weapon_size,
774                                                 ammo_color,
775                                                 ammo_alpha,
776                                                 DRAWFLAG_NORMAL
777                                         );
778
779                                         drawresetcliparea();
780                                 }
781                         }
782                 }
783                 else // draw a "ghost weapon icon" if you don't have the weapon
784                 {
785                         drawpic_aspect_skin(noncurrent_pos, self.model2, noncurrent_size, '0.2 0.2 0.2', panel_fg_alpha * 0.5, DRAWFLAG_NORMAL);
786                 }
787
788                 // draw the complain message
789                 if(self.weapon == complain_weapon)
790                 {
791                         if(fadetime)
792                                 a = ((complain_weapon_time + when > time) ? 1 : bound(0, (complain_weapon_time + when + fadetime - time) / fadetime, 1));
793                         else
794                                 a = ((complain_weapon_time + when > time) ? 1 : 0);
795
796                         string s;
797                         if(complain_weapon_type == 0) {
798                                 s = _("Out of ammo");
799                                 color = stov(autocvar_hud_panel_weapons_complainbubble_color_outofammo);
800                         }
801                         else if(complain_weapon_type == 1) {
802                                 s = _("Don't have");
803                                 color = stov(autocvar_hud_panel_weapons_complainbubble_color_donthave);
804                         }
805                         else {
806                                 s = _("Unavailable");
807                                 color = stov(autocvar_hud_panel_weapons_complainbubble_color_unavailable);
808                         }
809                         float padding = autocvar_hud_panel_weapons_complainbubble_padding;
810                         drawpic_aspect_skin(weapon_pos + '1 1 0' * padding, "weapon_complainbubble", weapon_size - '2 2 0' * padding, color, a * panel_fg_alpha, DRAWFLAG_NORMAL);
811                         drawstring_aspect(weapon_pos + '1 1 0' * padding, s, weapon_size - '2 2 0' * padding, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
812                 }
813
814                 #if 0
815                 /// debug code
816                 if(!autocvar_hud_panel_weapons_onlyowned)
817                 {
818                         drawfill(weapon_pos + '1 1 0', weapon_size - '2 2 0', '1 1 1', panel_fg_alpha * 0.2, DRAWFLAG_NORMAL);
819                         drawstring(weapon_pos, ftos(i + 1), label_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
820                 }
821                 #endif
822
823                 // continue with new position for the next weapon
824                 if(vertical_order)
825                 {
826                         ++column;
827                         if(column >= columns)
828                         {
829                                 column = 0;
830                                 ++row;
831                         }
832                 }
833                 else
834                 {
835                         ++row;
836                         if(row >= rows)
837                         {
838                                 row = 0;
839                                 ++column;
840                         }
841                 }
842         }
843 }
844
845 // Ammo (#1)
846 void DrawNadeProgressBar(vector myPos, vector mySize, float progress, vector color)
847 {
848         HUD_Panel_DrawProgressBar(
849                 myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize.x,
850                 mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize.x,
851                 autocvar_hud_panel_ammo_progressbar_name,
852                 progress, 0, 0, color,
853                 autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
854 }
855
856 void DrawAmmoNades(vector myPos, vector mySize, bool draw_expanding, float expand_time)
857 {
858         float bonusNades    = getstatf(STAT_NADE_BONUS);
859         float bonusProgress = getstatf(STAT_NADE_BONUS_SCORE);
860         float bonusType     = getstati(STAT_NADE_BONUS_TYPE);
861         vector nadeColor    = Nades[bonusType].m_color;
862         string nadeIcon     = Nades[bonusType].m_icon;
863
864         vector iconPos, textPos;
865
866         if(autocvar_hud_panel_ammo_iconalign)
867         {
868                 iconPos = myPos + eX * 2 * mySize.y;
869                 textPos = myPos;
870         }
871         else
872         {
873                 iconPos = myPos;
874                 textPos = myPos + eX * mySize.y;
875         }
876
877         if(bonusNades > 0 || bonusProgress > 0)
878         {
879                 DrawNadeProgressBar(myPos, mySize, bonusProgress, nadeColor);
880
881                 if(autocvar_hud_panel_ammo_text)
882                         drawstring_aspect(textPos, ftos(bonusNades), eX * (2/3) * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
883
884                 if(draw_expanding)
885                         drawpic_aspect_skin_expanding(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, expand_time);
886
887                 drawpic_aspect_skin(iconPos, nadeIcon, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
888         }
889 }
890
891 void DrawAmmoItem(vector myPos, vector mySize, .int ammoType, bool isCurrent, bool isInfinite)
892 {
893         if(ammoType == ammo_none)
894                 return;
895
896         // Initialize variables
897
898         int ammo;
899         if(autocvar__hud_configure)
900         {
901                 isCurrent = (ammoType == ammo_rockets); // Rockets always current
902                 ammo = 60;
903         }
904         else
905                 ammo = getstati(GetAmmoStat(ammoType));
906
907         if(!isCurrent)
908         {
909                 float scale = bound(0, autocvar_hud_panel_ammo_noncurrent_scale, 1);
910                 myPos = myPos + (mySize - mySize * scale) * 0.5;
911                 mySize = mySize * scale;
912         }
913
914         vector iconPos, textPos;
915         if(autocvar_hud_panel_ammo_iconalign)
916         {
917                 iconPos = myPos + eX * 2 * mySize.y;
918                 textPos = myPos;
919         }
920         else
921         {
922                 iconPos = myPos;
923                 textPos = myPos + eX * mySize.y;
924         }
925
926         bool isShadowed = (ammo <= 0 && !isCurrent && !isInfinite);
927
928         vector iconColor = isShadowed ? '0 0 0' : '1 1 1';
929         vector textColor;
930         if(isInfinite)
931                 textColor = '0.2 0.95 0';
932         else if(isShadowed)
933                 textColor = '0 0 0';
934         else if(ammo < 10)
935                 textColor = '0.8 0.04 0';
936         else
937                 textColor = '1 1 1';
938
939         float alpha;
940         if(isCurrent)
941                 alpha = panel_fg_alpha;
942         else if(isShadowed)
943                 alpha = panel_fg_alpha * bound(0, autocvar_hud_panel_ammo_noncurrent_alpha, 1) * 0.5;
944         else
945                 alpha = panel_fg_alpha * bound(0, autocvar_hud_panel_ammo_noncurrent_alpha, 1);
946
947         string text = isInfinite ? "\xE2\x88\x9E" : ftos(ammo); // Use infinity symbol (U+221E)
948
949         // Draw item
950
951         if(isCurrent)
952                 drawpic_aspect_skin(myPos, "ammo_current_bg", mySize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
953
954         if(ammo > 0 && autocvar_hud_panel_ammo_progressbar)
955                 HUD_Panel_DrawProgressBar(myPos + eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize.x, mySize - eX * autocvar_hud_panel_ammo_progressbar_xoffset * mySize.x, autocvar_hud_panel_ammo_progressbar_name, ammo/autocvar_hud_panel_ammo_maxammo, 0, 0, textColor, autocvar_hud_progressbar_alpha * alpha, DRAWFLAG_NORMAL);
956
957         if(autocvar_hud_panel_ammo_text)
958                 drawstring_aspect(textPos, text, eX * (2/3) * mySize.x + eY * mySize.y, textColor, alpha, DRAWFLAG_NORMAL);
959
960         drawpic_aspect_skin(iconPos, GetAmmoPicture(ammoType), '1 1 0' * mySize.y, iconColor, alpha, DRAWFLAG_NORMAL);
961 }
962
963 int nade_prevstatus;
964 int nade_prevframe;
965 float nade_statuschange_time;
966 void HUD_Ammo(void)
967 {
968         if(hud != HUD_NORMAL) return;
969         if(!autocvar__hud_configure)
970         {
971                 if(!autocvar_hud_panel_ammo) return;
972                 if(spectatee_status == -1) return;
973         }
974
975         HUD_Panel_UpdateCvars();
976
977         draw_beginBoldFont();
978
979         vector pos, mySize;
980         pos = panel_pos;
981         mySize = panel_size;
982
983         HUD_Panel_DrawBg(1);
984         if(panel_bg_padding)
985         {
986                 pos += '1 1 0' * panel_bg_padding;
987                 mySize -= '2 2 0' * panel_bg_padding;
988         }
989
990         int rows = 0, columns, row, column;
991         float nade_cnt = getstatf(STAT_NADE_BONUS), nade_score = getstatf(STAT_NADE_BONUS_SCORE);
992         bool draw_nades = (nade_cnt > 0 || nade_score > 0);
993         float nade_statuschange_elapsedtime;
994         int total_ammo_count;
995
996         vector ammo_size;
997         if (autocvar_hud_panel_ammo_onlycurrent)
998                 total_ammo_count = 1;
999         else
1000                 total_ammo_count = AMMO_COUNT;
1001
1002         if(draw_nades)
1003         {
1004                 ++total_ammo_count;
1005                 if (nade_cnt != nade_prevframe)
1006                 {
1007                         nade_statuschange_time = time;
1008                         nade_prevstatus = nade_prevframe;
1009                         nade_prevframe = nade_cnt;
1010                 }
1011         }
1012         else
1013                 nade_prevstatus = nade_prevframe = nade_statuschange_time = 0;
1014
1015         rows = HUD_GetRowCount(total_ammo_count, mySize, 3);
1016         columns = ceil((total_ammo_count)/rows);
1017         ammo_size = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
1018
1019         vector offset = '0 0 0'; // fteqcc sucks
1020         float newSize;
1021         if(ammo_size.x/ammo_size.y > 3)
1022         {
1023                 newSize = 3 * ammo_size.y;
1024                 offset.x = ammo_size.x - newSize;
1025                 pos.x += offset.x/2;
1026                 ammo_size.x = newSize;
1027         }
1028         else
1029         {
1030                 newSize = 1/3 * ammo_size.x;
1031                 offset.y = ammo_size.y - newSize;
1032                 pos.y += offset.y/2;
1033                 ammo_size.y = newSize;
1034         }
1035
1036         int i;
1037         bool infinite_ammo = (getstati(STAT_ITEMS, 0, 24) & IT_UNLIMITED_WEAPON_AMMO);
1038         row = column = 0;
1039         if(autocvar_hud_panel_ammo_onlycurrent)
1040         {
1041                 if(autocvar__hud_configure)
1042                 {
1043                         DrawAmmoItem(pos, ammo_size, ammo_rockets, true, false);
1044                 }
1045                 else
1046                 {
1047                         DrawAmmoItem(
1048                                 pos,
1049                                 ammo_size,
1050                                 (get_weaponinfo(switchweapon)).ammo_field,
1051                                 true,
1052                                 infinite_ammo
1053                         );
1054                 }
1055
1056                 ++row;
1057                 if(row >= rows)
1058                 {
1059                         row = 0;
1060                         column = column + 1;
1061                 }
1062         }
1063         else
1064         {
1065                 .int ammotype;
1066                 row = column = 0;
1067                 for(i = 0; i < AMMO_COUNT; ++i)
1068                 {
1069                         ammotype = GetAmmoFieldFromNum(i);
1070                         DrawAmmoItem(
1071                                 pos + eX * column * (ammo_size.x + offset.x) + eY * row * (ammo_size.y + offset.y),
1072                                 ammo_size,
1073                                 ammotype,
1074                                 ((get_weaponinfo(switchweapon)).ammo_field == ammotype),
1075                                 infinite_ammo
1076                         );
1077
1078                         ++row;
1079                         if(row >= rows)
1080                         {
1081                                 row = 0;
1082                                 column = column + 1;
1083                         }
1084                 }
1085         }
1086
1087         if (draw_nades)
1088         {
1089                 nade_statuschange_elapsedtime = time - nade_statuschange_time;
1090
1091                 float f = bound(0, nade_statuschange_elapsedtime*2, 1);
1092
1093                 DrawAmmoNades(pos + eX * column * (ammo_size.x + offset.x) + eY * row * (ammo_size.y + offset.y), ammo_size, nade_prevstatus < nade_cnt && nade_cnt != 0 && f < 1, f);
1094         }
1095
1096         draw_endBoldFont();
1097 }
1098
1099 void DrawNumIcon_expanding(vector myPos, vector mySize, float x, string icon, bool vertical, bool icon_right_align, vector color, float theAlpha, float fadelerp)
1100 {
1101         vector newPos = '0 0 0', newSize = '0 0 0';
1102         vector picpos, numpos;
1103
1104         if (vertical)
1105         {
1106                 if(mySize.y/mySize.x > 2)
1107                 {
1108                         newSize.y = 2 * mySize.x;
1109                         newSize.x = mySize.x;
1110
1111                         newPos.y = myPos.y + (mySize.y - newSize.y) / 2;
1112                         newPos.x = myPos.x;
1113                 }
1114                 else
1115                 {
1116                         newSize.x = 1/2 * mySize.y;
1117                         newSize.y = mySize.y;
1118
1119                         newPos.x = myPos.x + (mySize.x - newSize.x) / 2;
1120                         newPos.y = myPos.y;
1121                 }
1122
1123                 if(icon_right_align)
1124                 {
1125                         numpos = newPos;
1126                         picpos = newPos + eY * newSize.x;
1127                 }
1128                 else
1129                 {
1130                         picpos = newPos;
1131                         numpos = newPos + eY * newSize.x;
1132                 }
1133
1134                 newSize.y /= 2;
1135                 drawpic_aspect_skin(picpos, icon, newSize, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
1136                 // make number smaller than icon, it looks better
1137                 // reduce only y to draw numbers with different number of digits with the same y size
1138                 numpos.y += newSize.y * ((1 - 0.7) / 2);
1139                 newSize.y *= 0.7;
1140                 drawstring_aspect(numpos, ftos(x), newSize, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL);
1141                 return;
1142         }
1143
1144         if(mySize.x/mySize.y > 3)
1145         {
1146                 newSize.x = 3 * mySize.y;
1147                 newSize.y = mySize.y;
1148
1149                 newPos.x = myPos.x + (mySize.x - newSize.x) / 2;
1150                 newPos.y = myPos.y;
1151         }
1152         else
1153         {
1154                 newSize.y = 1/3 * mySize.x;
1155                 newSize.x = mySize.x;
1156
1157                 newPos.y = myPos.y + (mySize.y - newSize.y) / 2;
1158                 newPos.x = myPos.x;
1159         }
1160
1161         if(icon_right_align) // right align
1162         {
1163                 numpos = newPos;
1164                 picpos = newPos + eX * 2 * newSize.y;
1165         }
1166         else // left align
1167         {
1168                 numpos = newPos + eX * newSize.y;
1169                 picpos = newPos;
1170         }
1171
1172         // NOTE: newSize_x is always equal to 3 * mySize_y so we can use
1173         // '2 1 0' * newSize_y instead of eX * (2/3) * newSize_x + eY * newSize_y
1174         drawstring_aspect_expanding(numpos, ftos(x), '2 1 0' * newSize.y, color, panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, fadelerp);
1175         drawpic_aspect_skin_expanding(picpos, icon, '1 1 0' * newSize.y, '1 1 1', panel_fg_alpha * theAlpha, DRAWFLAG_NORMAL, fadelerp);
1176 }
1177
1178 void DrawNumIcon(vector myPos, vector mySize, float x, string icon, bool vertical, bool icon_right_align, vector color, float theAlpha)
1179 {
1180         DrawNumIcon_expanding(myPos, mySize, x, icon, vertical, icon_right_align, color, theAlpha, 0);
1181 }
1182
1183 // Powerups (#2)
1184 //
1185
1186 // Powerup item fields (reusing existing fields)
1187 .string message;  // Human readable name
1188 .string netname;  // Icon name
1189 .vector colormod; // Color
1190 .float count;     // Time left
1191 .float lifetime;  // Maximum time
1192
1193 entity powerupItems;
1194 int powerupItemsCount;
1195
1196 void resetPowerupItems()
1197 {
1198         entity item;
1199         for(item = powerupItems; item; item = item.chain)
1200                 item.count = 0;
1201
1202         powerupItemsCount = 0;
1203 }
1204
1205 void addPowerupItem(string name, string icon, vector color, float currentTime, float lifeTime)
1206 {
1207         if(!powerupItems)
1208                 powerupItems = spawn();
1209
1210         entity item;
1211         for(item = powerupItems; item.count; item = item.chain)
1212                 if(!item.chain)
1213                         item.chain = spawn();
1214
1215         item.message  = name;
1216         item.netname  = icon;
1217         item.colormod = color;
1218         item.count    = currentTime;
1219         item.lifetime = lifeTime;
1220
1221         ++powerupItemsCount;
1222 }
1223
1224 int getPowerupItemAlign(int align, int column, int row, int columns, int rows, bool isVertical)
1225 {
1226         if(align < 2)
1227                 return align;
1228
1229         bool isTop    =  isVertical && rows > 1 && row == 0;
1230         bool isBottom =  isVertical && rows > 1 && row == rows-1;
1231         bool isLeft   = !isVertical && columns > 1 && column == 0;
1232         bool isRight  = !isVertical && columns > 1 && column == columns-1;
1233
1234         if(isTop    || isLeft)  return (align == 2) ? 1 : 0;
1235         if(isBottom || isRight) return (align == 2) ? 0 : 1;
1236
1237         return 2;
1238 }
1239
1240 void HUD_Powerups()
1241 {
1242         int allItems = getstati(STAT_ITEMS, 0, 24);
1243         int allBuffs = getstati(STAT_BUFFS, 0, 24);
1244         int strengthTime, shieldTime, superTime;
1245
1246         // Initialize items
1247         if(!autocvar__hud_configure)
1248         {
1249                 if(!autocvar_hud_panel_powerups) return;
1250                 if(spectatee_status == -1) return;
1251                 if(getstati(STAT_HEALTH) <= 0) return;
1252                 if(!(allItems & (ITEM_Strength.m_itemid | ITEM_Shield.m_itemid | IT_SUPERWEAPON)) && !allBuffs) return;
1253
1254                 strengthTime = bound(0, getstatf(STAT_STRENGTH_FINISHED) - time, 99);
1255                 shieldTime = bound(0, getstatf(STAT_INVINCIBLE_FINISHED) - time, 99);
1256                 superTime = bound(0, getstatf(STAT_SUPERWEAPONS_FINISHED) - time, 99);
1257
1258                 if(allItems & IT_UNLIMITED_SUPERWEAPONS)
1259                         superTime = 99;
1260
1261                 // Prevent stuff to show up on mismatch that will be fixed next frame
1262                 if(!(allItems & IT_SUPERWEAPON))
1263                         superTime = 0;
1264         }
1265         else
1266         {
1267                 strengthTime = 15;
1268                 shieldTime = 27;
1269                 superTime = 13;
1270                 allBuffs = 0;
1271         }
1272
1273         // Add items to linked list
1274         resetPowerupItems();
1275
1276         if(strengthTime)
1277                 addPowerupItem("Strength", "strength", autocvar_hud_progressbar_strength_color, strengthTime, 30);
1278         if(shieldTime)
1279                 addPowerupItem("Shield", "shield", autocvar_hud_progressbar_shield_color, shieldTime, 30);
1280         if(superTime)
1281                 addPowerupItem("Superweapons", "superweapons", autocvar_hud_progressbar_superweapons_color, superTime, 30);
1282
1283         FOREACH(Buffs, it.m_itemid & allBuffs, LAMBDA(
1284                 addPowerupItem(it.m_prettyName, strcat("buff_", it.m_name), it.m_color, bound(0, getstatf(STAT_BUFF_TIME) - time, 99), 60);
1285         ));
1286
1287         if(!powerupItemsCount)
1288                 return;
1289
1290         // Draw panel background
1291         HUD_Panel_UpdateCvars();
1292         HUD_Panel_DrawBg(1);
1293
1294         // Set drawing area
1295         vector pos = panel_pos;
1296         vector size = panel_size;
1297         bool isVertical = size.y > size.x;
1298
1299         if(panel_bg_padding)
1300         {
1301                 pos += '1 1 0' * panel_bg_padding;
1302                 size -= '2 2 0' * panel_bg_padding;
1303         }
1304
1305         // Find best partitioning of the drawing area
1306         const float DESIRED_ASPECT = 6;
1307         float aspect = 0, a;
1308         int columns = 0, c;
1309         int rows = 0, r;
1310         int i = 1;
1311
1312         do
1313         {
1314                 c = floor(powerupItemsCount / i);
1315                 r = ceil(powerupItemsCount / c);
1316                 a = isVertical ? (size.y/r) / (size.x/c) : (size.x/c) / (size.y/r);
1317
1318                 if(i == 1 || fabs(DESIRED_ASPECT - a) < fabs(DESIRED_ASPECT - aspect))
1319                 {
1320                         aspect = a;
1321                         columns = c;
1322                         rows = r;
1323                 }
1324         }
1325         while(++i <= powerupItemsCount);
1326
1327         // Prevent single items from getting too wide
1328         if(powerupItemsCount == 1 && aspect > DESIRED_ASPECT)
1329         {
1330                 if(isVertical)
1331                 {
1332                         size.y *= 0.5;
1333                         pos.y += size.y * 0.5;
1334                 }
1335                 else
1336                 {
1337                         size.x *= 0.5;
1338                         pos.x += size.x * 0.5;
1339                 }
1340         }
1341
1342         // Draw items from linked list
1343         vector itemPos = pos;
1344         vector itemSize = eX * (size.x / columns) + eY * (size.y / rows);
1345         vector textColor = '1 1 1';
1346
1347         int fullSeconds = 0;
1348         int align = 0;
1349         int column = 0;
1350         int row = 0;
1351
1352         draw_beginBoldFont();
1353         for(entity item = powerupItems; item.count; item = item.chain)
1354         {
1355                 itemPos = eX * (pos.x + column * itemSize.x) + eY * (pos.y + row * itemSize.y);
1356
1357                 // Draw progressbar
1358                 if(autocvar_hud_panel_powerups_progressbar)
1359                 {
1360                         align = getPowerupItemAlign(autocvar_hud_panel_powerups_baralign, column, row, columns, rows, isVertical);
1361                         HUD_Panel_DrawProgressBar(itemPos, itemSize, "progressbar", item.count / item.lifetime, isVertical, align, item.colormod, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
1362                 }
1363
1364                 // Draw icon and text
1365                 if(autocvar_hud_panel_powerups_text)
1366                 {
1367                         align = getPowerupItemAlign(autocvar_hud_panel_powerups_iconalign, column, row, columns, rows, isVertical);
1368                         fullSeconds = ceil(item.count);
1369                         textColor = '0.6 0.6 0.6' + (item.colormod * 0.4);
1370
1371                         if(item.count > 1)
1372                                 DrawNumIcon(itemPos, itemSize, fullSeconds, item.netname, isVertical, align, textColor, panel_fg_alpha);
1373                         if(item.count <= 5)
1374                                 DrawNumIcon_expanding(itemPos, itemSize, fullSeconds, item.netname, isVertical, align, textColor, panel_fg_alpha, bound(0, (fullSeconds - item.count) / 0.5, 1));
1375                 }
1376
1377                 // Determine next section
1378                 if(isVertical)
1379                 {
1380                         if(++column >= columns)
1381                         {
1382                                 column = 0;
1383                                 ++row;
1384                         }
1385                 }
1386                 else
1387                 {
1388                         if(++row >= rows)
1389                         {
1390                                 row = 0;
1391                                 ++column;
1392                         }
1393                 }
1394         }
1395         draw_endBoldFont();
1396 }
1397
1398 // Health/armor (#3)
1399 //
1400
1401
1402 void HUD_HealthArmor(void)
1403 {
1404         int armor, health, fuel;
1405         if(!autocvar__hud_configure)
1406         {
1407                 if(!autocvar_hud_panel_healtharmor) return;
1408                 if(hud != HUD_NORMAL) return;
1409                 if(spectatee_status == -1) return;
1410
1411                 health = getstati(STAT_HEALTH);
1412                 if(health <= 0)
1413                 {
1414                         prev_health = -1;
1415                         return;
1416                 }
1417                 armor = getstati(STAT_ARMOR);
1418
1419                 // code to check for spectatee_status changes is in Ent_ClientData()
1420                 // prev_p_health and prev_health can be set to -1 there
1421
1422                 if (prev_p_health == -1)
1423                 {
1424                         // no effect
1425                         health_beforedamage = 0;
1426                         armor_beforedamage = 0;
1427                         health_damagetime = 0;
1428                         armor_damagetime = 0;
1429                         prev_health = health;
1430                         prev_armor = armor;
1431                         old_p_health = health;
1432                         old_p_armor = armor;
1433                         prev_p_health = health;
1434                         prev_p_armor = armor;
1435                 }
1436                 else if (prev_health == -1)
1437                 {
1438                         //start the load effect
1439                         health_damagetime = 0;
1440                         armor_damagetime = 0;
1441                         prev_health = 0;
1442                         prev_armor = 0;
1443                 }
1444                 fuel = getstati(STAT_FUEL);
1445         }
1446         else
1447         {
1448                 health = 150;
1449                 armor = 75;
1450                 fuel = 20;
1451         }
1452
1453         HUD_Panel_UpdateCvars();
1454
1455         draw_beginBoldFont();
1456
1457         vector pos, mySize;
1458         pos = panel_pos;
1459         mySize = panel_size;
1460
1461         HUD_Panel_DrawBg(1);
1462         if(panel_bg_padding)
1463         {
1464                 pos += '1 1 0' * panel_bg_padding;
1465                 mySize -= '2 2 0' * panel_bg_padding;
1466         }
1467
1468         int baralign = autocvar_hud_panel_healtharmor_baralign;
1469         int iconalign = autocvar_hud_panel_healtharmor_iconalign;
1470
1471     int maxhealth = autocvar_hud_panel_healtharmor_maxhealth;
1472     int maxarmor = autocvar_hud_panel_healtharmor_maxarmor;
1473         if(autocvar_hud_panel_healtharmor == 2) // combined health and armor display
1474         {
1475                 vector v;
1476                 v = healtharmor_maxdamage(health, armor, armorblockpercent, DEATH_WEAPON);
1477
1478                 float x;
1479                 x = floor(v.x + 1);
1480
1481         float maxtotal = maxhealth + maxarmor;
1482                 string biggercount;
1483                 if(v.z) // NOT fully armored
1484                 {
1485                         biggercount = "health";
1486                         if(autocvar_hud_panel_healtharmor_progressbar)
1487                                 HUD_Panel_DrawProgressBar(pos, mySize, autocvar_hud_panel_healtharmor_progressbar_health, x/maxtotal, 0, (baralign == 1 || baralign == 2), autocvar_hud_progressbar_health_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
1488                         if(armor)
1489             if(autocvar_hud_panel_healtharmor_text)
1490                                 drawpic_aspect_skin(pos + eX * mySize.x - eX * 0.5 * mySize.y, "armor", '0.5 0.5 0' * mySize.y, '1 1 1', panel_fg_alpha * armor / health, DRAWFLAG_NORMAL);
1491                 }
1492                 else
1493                 {
1494                         biggercount = "armor";
1495                         if(autocvar_hud_panel_healtharmor_progressbar)
1496                                 HUD_Panel_DrawProgressBar(pos, mySize, autocvar_hud_panel_healtharmor_progressbar_armor, x/maxtotal, 0, (baralign == 1 || baralign == 2), autocvar_hud_progressbar_armor_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
1497                         if(health)
1498             if(autocvar_hud_panel_healtharmor_text)
1499                                 drawpic_aspect_skin(pos + eX * mySize.x - eX * 0.5 * mySize.y, "health", '0.5 0.5 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1500                 }
1501         if(autocvar_hud_panel_healtharmor_text)
1502                         DrawNumIcon(pos, mySize, x, biggercount, 0, iconalign, HUD_Get_Num_Color(x, maxtotal), 1);
1503
1504                 if(fuel)
1505                         HUD_Panel_DrawProgressBar(pos, eX * mySize.x + eY * 0.2 * mySize.y, "progressbar", fuel/100, 0, (baralign == 1 || baralign == 3), autocvar_hud_progressbar_fuel_color, panel_fg_alpha * 0.8, DRAWFLAG_NORMAL);
1506         }
1507         else
1508         {
1509                 float panel_ar = mySize.x/mySize.y;
1510                 bool is_vertical = (panel_ar < 1);
1511                 vector health_offset = '0 0 0', armor_offset = '0 0 0';
1512                 if (panel_ar >= 4 || (panel_ar >= 1/4 && panel_ar < 1))
1513                 {
1514                         mySize.x *= 0.5;
1515                         if (autocvar_hud_panel_healtharmor_flip)
1516                                 health_offset.x = mySize.x;
1517                         else
1518                                 armor_offset.x = mySize.x;
1519                 }
1520                 else
1521                 {
1522                         mySize.y *= 0.5;
1523                         if (autocvar_hud_panel_healtharmor_flip)
1524                                 health_offset.y = mySize.y;
1525                         else
1526                                 armor_offset.y = mySize.y;
1527                 }
1528
1529                 bool health_baralign, armor_baralign, fuel_baralign;
1530                 bool health_iconalign, armor_iconalign;
1531                 if (autocvar_hud_panel_healtharmor_flip)
1532                 {
1533                         armor_baralign = (autocvar_hud_panel_healtharmor_baralign == 2 || autocvar_hud_panel_healtharmor_baralign == 1);
1534                         health_baralign = (autocvar_hud_panel_healtharmor_baralign == 3 || autocvar_hud_panel_healtharmor_baralign == 1);
1535                         fuel_baralign = health_baralign;
1536                         armor_iconalign = (autocvar_hud_panel_healtharmor_iconalign == 2 || autocvar_hud_panel_healtharmor_iconalign == 1);
1537                         health_iconalign = (autocvar_hud_panel_healtharmor_iconalign == 3 || autocvar_hud_panel_healtharmor_iconalign == 1);
1538                 }
1539                 else
1540                 {
1541                         health_baralign = (autocvar_hud_panel_healtharmor_baralign == 2 || autocvar_hud_panel_healtharmor_baralign == 1);
1542                         armor_baralign = (autocvar_hud_panel_healtharmor_baralign == 3 || autocvar_hud_panel_healtharmor_baralign == 1);
1543                         fuel_baralign = armor_baralign;
1544                         health_iconalign = (autocvar_hud_panel_healtharmor_iconalign == 2 || autocvar_hud_panel_healtharmor_iconalign == 1);
1545                         armor_iconalign = (autocvar_hud_panel_healtharmor_iconalign == 3 || autocvar_hud_panel_healtharmor_iconalign == 1);
1546                 }
1547
1548                 //if(health)
1549                 {
1550                         if(autocvar_hud_panel_healtharmor_progressbar)
1551                         {
1552                                 float p_health, pain_health_alpha;
1553                                 p_health = health;
1554                                 pain_health_alpha = 1;
1555                                 if (autocvar_hud_panel_healtharmor_progressbar_gfx)
1556                                 {
1557                                         if (autocvar_hud_panel_healtharmor_progressbar_gfx_smooth > 0)
1558                                         {
1559                                                 if (fabs(prev_health - health) >= autocvar_hud_panel_healtharmor_progressbar_gfx_smooth)
1560                                                 {
1561                                                         if (time - old_p_healthtime < 1)
1562                                                                 old_p_health = prev_p_health;
1563                                                         else
1564                                                                 old_p_health = prev_health;
1565                                                         old_p_healthtime = time;
1566                                                 }
1567                                                 if (time - old_p_healthtime < 1)
1568                                                 {
1569                                                         p_health += (old_p_health - health) * (1 - (time - old_p_healthtime));
1570                                                         prev_p_health = p_health;
1571                                                 }
1572                                         }
1573                                         if (autocvar_hud_panel_healtharmor_progressbar_gfx_damage > 0)
1574                                         {
1575                                                 if (prev_health - health >= autocvar_hud_panel_healtharmor_progressbar_gfx_damage)
1576                                                 {
1577                                                         if (time - health_damagetime >= 1)
1578                                                                 health_beforedamage = prev_health;
1579                                                         health_damagetime = time;
1580                                                 }
1581                                                 if (time - health_damagetime < 1)
1582                                                 {
1583                                                         float health_damagealpha = 1 - (time - health_damagetime)*(time - health_damagetime);
1584                                                         HUD_Panel_DrawProgressBar(pos + health_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_health, health_beforedamage/maxhealth, is_vertical, health_baralign, autocvar_hud_progressbar_health_color, autocvar_hud_progressbar_alpha * panel_fg_alpha * health_damagealpha, DRAWFLAG_NORMAL);
1585                                                 }
1586                                         }
1587                                         prev_health = health;
1588
1589                                         if (health <= autocvar_hud_panel_healtharmor_progressbar_gfx_lowhealth)
1590                                         {
1591                                                 float BLINK_FACTOR = 0.15;
1592                                                 float BLINK_BASE = 0.85;
1593                                                 float BLINK_FREQ = 9;
1594                                                 pain_health_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
1595                                         }
1596                                 }
1597                                 HUD_Panel_DrawProgressBar(pos + health_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_health, p_health/maxhealth, is_vertical, health_baralign, autocvar_hud_progressbar_health_color, autocvar_hud_progressbar_alpha * panel_fg_alpha * pain_health_alpha, DRAWFLAG_NORMAL);
1598                         }
1599                         if(autocvar_hud_panel_healtharmor_text)
1600                                 DrawNumIcon(pos + health_offset, mySize, health, "health", is_vertical, health_iconalign, HUD_Get_Num_Color(health, maxhealth), 1);
1601                 }
1602
1603                 if(armor)
1604                 {
1605                         if(autocvar_hud_panel_healtharmor_progressbar)
1606                         {
1607                                 float p_armor;
1608                                 p_armor = armor;
1609                                 if (autocvar_hud_panel_healtharmor_progressbar_gfx)
1610                                 {
1611                                         if (autocvar_hud_panel_healtharmor_progressbar_gfx_smooth > 0)
1612                                         {
1613                                                 if (fabs(prev_armor - armor) >= autocvar_hud_panel_healtharmor_progressbar_gfx_smooth)
1614                                                 {
1615                                                         if (time - old_p_armortime < 1)
1616                                                                 old_p_armor = prev_p_armor;
1617                                                         else
1618                                                                 old_p_armor = prev_armor;
1619                                                         old_p_armortime = time;
1620                                                 }
1621                                                 if (time - old_p_armortime < 1)
1622                                                 {
1623                                                         p_armor += (old_p_armor - armor) * (1 - (time - old_p_armortime));
1624                                                         prev_p_armor = p_armor;
1625                                                 }
1626                                         }
1627                                         if (autocvar_hud_panel_healtharmor_progressbar_gfx_damage > 0)
1628                                         {
1629                                                 if (prev_armor - armor >= autocvar_hud_panel_healtharmor_progressbar_gfx_damage)
1630                                                 {
1631                                                         if (time - armor_damagetime >= 1)
1632                                                                 armor_beforedamage = prev_armor;
1633                                                         armor_damagetime = time;
1634                                                 }
1635                                                 if (time - armor_damagetime < 1)
1636                                                 {
1637                                                         float armor_damagealpha = 1 - (time - armor_damagetime)*(time - armor_damagetime);
1638                                                         HUD_Panel_DrawProgressBar(pos + armor_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_armor, armor_beforedamage/maxarmor, is_vertical, armor_baralign, autocvar_hud_progressbar_armor_color, autocvar_hud_progressbar_alpha * panel_fg_alpha * armor_damagealpha, DRAWFLAG_NORMAL);
1639                                                 }
1640                                         }
1641                                         prev_armor = armor;
1642                                 }
1643                                 HUD_Panel_DrawProgressBar(pos + armor_offset, mySize, autocvar_hud_panel_healtharmor_progressbar_armor, p_armor/maxarmor, is_vertical, armor_baralign, autocvar_hud_progressbar_armor_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
1644                         }
1645                         if(autocvar_hud_panel_healtharmor_text)
1646                                 DrawNumIcon(pos + armor_offset, mySize, armor, "armor", is_vertical, armor_iconalign, HUD_Get_Num_Color(armor, maxarmor), 1);
1647                 }
1648
1649                 if(fuel)
1650                 {
1651                         if (is_vertical)
1652                                 mySize.x *= 0.2 / 2; //if vertical always halve x to not cover too much numbers with 3 digits
1653                         else
1654                                 mySize.y *= 0.2;
1655                         if (panel_ar >= 4)
1656                                 mySize.x *= 2; //restore full panel size
1657                         else if (panel_ar < 1/4)
1658                                 mySize.y *= 2; //restore full panel size
1659                         HUD_Panel_DrawProgressBar(pos, mySize, "progressbar", fuel/100, is_vertical, fuel_baralign, autocvar_hud_progressbar_fuel_color, panel_fg_alpha * 0.8, DRAWFLAG_NORMAL);
1660                 }
1661         }
1662
1663         draw_endBoldFont();
1664 }
1665
1666 // Notification area (#4)
1667 //
1668
1669 void HUD_Notify_Push(string icon, string attacker, string victim)
1670 {
1671         if (icon == "")
1672                 return;
1673
1674         ++notify_count;
1675         --notify_index;
1676
1677         if (notify_index == -1)
1678                 notify_index = NOTIFY_MAX_ENTRIES-1;
1679
1680         // Free old strings
1681         if (notify_attackers[notify_index])
1682                 strunzone(notify_attackers[notify_index]);
1683
1684         if (notify_victims[notify_index])
1685                 strunzone(notify_victims[notify_index]);
1686
1687         if (notify_icons[notify_index])
1688                 strunzone(notify_icons[notify_index]);
1689
1690         // Allocate new strings
1691         if (victim != "")
1692         {
1693                 notify_attackers[notify_index] = strzone(attacker);
1694                 notify_victims[notify_index] = strzone(victim);
1695         }
1696         else
1697         {
1698                 // In case of a notification without a victim, the attacker
1699                 // is displayed on the victim's side. Instead of special
1700                 // treatment later on, we can simply switch them here.
1701                 notify_attackers[notify_index] = string_null;
1702                 notify_victims[notify_index] = strzone(attacker);
1703         }
1704
1705         notify_icons[notify_index] = strzone(icon);
1706         notify_times[notify_index] = time;
1707 }
1708
1709 void HUD_Notify(void)
1710 {
1711         if (!autocvar__hud_configure)
1712                 if (!autocvar_hud_panel_notify)
1713                         return;
1714
1715         HUD_Panel_UpdateCvars();
1716         HUD_Panel_DrawBg(1);
1717
1718         if (!autocvar__hud_configure)
1719                 if (notify_count == 0)
1720                         return;
1721
1722         vector pos, size;
1723         pos  = panel_pos;
1724         size = panel_size;
1725
1726         if (panel_bg_padding)
1727         {
1728                 pos  += '1 1 0' * panel_bg_padding;
1729                 size -= '2 2 0' * panel_bg_padding;
1730         }
1731
1732         float fade_start = max(0, autocvar_hud_panel_notify_time);
1733         float fade_time = max(0, autocvar_hud_panel_notify_fadetime);
1734         float icon_aspect = max(1, autocvar_hud_panel_notify_icon_aspect);
1735
1736         int entry_count = bound(1, floor(NOTIFY_MAX_ENTRIES * size.y / size.x), NOTIFY_MAX_ENTRIES);
1737         float entry_height = size.y / entry_count;
1738
1739         float panel_width_half = size.x * 0.5;
1740         float icon_width_half = entry_height * icon_aspect / 2;
1741         float name_maxwidth = panel_width_half - icon_width_half - size.x * NOTIFY_ICON_MARGIN;
1742
1743         vector font_size = '0.5 0.5 0' * entry_height * autocvar_hud_panel_notify_fontsize;
1744         vector icon_size = (eX * icon_aspect + eY) * entry_height;
1745         vector icon_left = eX * (panel_width_half - icon_width_half);
1746         vector attacker_right = eX * name_maxwidth;
1747         vector victim_left = eX * (size.x - name_maxwidth);
1748
1749         vector attacker_pos, victim_pos, icon_pos;
1750         string attacker, victim, icon;
1751         int i, j, count, step, limit;
1752         float alpha;
1753
1754         if (autocvar_hud_panel_notify_flip)
1755         {
1756                 // Order items from the top down
1757                 i = 0;
1758                 step = +1;
1759                 limit = entry_count;
1760         }
1761         else
1762         {
1763                 // Order items from the bottom up
1764                 i = entry_count - 1;
1765                 step = -1;
1766                 limit = -1;
1767         }
1768
1769         for (j = notify_index, count = 0; i != limit; i += step, ++j, ++count)
1770         {
1771                 if(autocvar__hud_configure)
1772                 {
1773                         attacker = sprintf(_("Player %d"), count + 1);
1774                         victim = sprintf(_("Player %d"), count + 2);
1775                         icon = get_weaponinfo(min(WEP_FIRST + count * 2, WEP_LAST)).model2;
1776                         alpha = bound(0, 1.2 - count / entry_count, 1);
1777                 }
1778                 else
1779                 {
1780                         if (j == NOTIFY_MAX_ENTRIES)
1781                                 j = 0;
1782
1783                         if (notify_times[j] + fade_start > time)
1784                                 alpha = 1;
1785                         else if (fade_time != 0)
1786                         {
1787                                 alpha = bound(0, (notify_times[j] + fade_start + fade_time - time) / fade_time, 1);
1788                                 if (alpha == 0)
1789                                         break;
1790                         }
1791                         else
1792                                 break;
1793
1794                         attacker = notify_attackers[j];
1795                         victim = notify_victims[j];
1796                         icon = notify_icons[j];
1797                 }
1798
1799                 if (icon != "" && victim != "")
1800                 {
1801                         vector name_top = eY * (i * entry_height + 0.5 * (entry_height - font_size.y));
1802
1803                         icon_pos = pos + icon_left + eY * i * entry_height;
1804                         drawpic_aspect_skin(icon_pos, icon, icon_size, '1 1 1', panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
1805
1806                         victim = textShortenToWidth(victim, name_maxwidth, font_size, stringwidth_colors);
1807                         victim_pos = pos + victim_left + name_top;
1808                         drawcolorcodedstring(victim_pos, victim, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
1809
1810                         if (attacker != "")
1811                         {
1812                                 attacker = textShortenToWidth(attacker, name_maxwidth, font_size, stringwidth_colors);
1813                                 attacker_pos = pos + attacker_right - eX * stringwidth(attacker, true, font_size) + name_top;
1814                                 drawcolorcodedstring(attacker_pos, attacker, font_size, panel_fg_alpha * alpha, DRAWFLAG_NORMAL);
1815                         }
1816                 }
1817         }
1818
1819         notify_count = count;
1820 }
1821
1822 void HUD_Timer(void)
1823 {
1824         if(!autocvar__hud_configure)
1825         {
1826                 if(!autocvar_hud_panel_timer) return;
1827         }
1828
1829         HUD_Panel_UpdateCvars();
1830
1831         draw_beginBoldFont();
1832
1833         vector pos, mySize;
1834         pos = panel_pos;
1835         mySize = panel_size;
1836
1837         HUD_Panel_DrawBg(1);
1838         if(panel_bg_padding)
1839         {
1840                 pos += '1 1 0' * panel_bg_padding;
1841                 mySize -= '2 2 0' * panel_bg_padding;
1842         }
1843
1844         string timer;
1845         float timelimit, elapsedTime, timeleft, minutesLeft;
1846
1847         timelimit = getstatf(STAT_TIMELIMIT);
1848
1849         timeleft = max(0, timelimit * 60 + getstatf(STAT_GAMESTARTTIME) - time);
1850         timeleft = ceil(timeleft);
1851
1852         minutesLeft = floor(timeleft / 60);
1853
1854         vector timer_color;
1855         if(minutesLeft >= 5 || warmup_stage || timelimit == 0) //don't use red or yellow in warmup or when there is no timelimit
1856                 timer_color = '1 1 1'; //white
1857         else if(minutesLeft >= 1)
1858                 timer_color = '1 1 0'; //yellow
1859         else
1860                 timer_color = '1 0 0'; //red
1861
1862         if (autocvar_hud_panel_timer_increment || timelimit == 0 || warmup_stage) {
1863                 if (time < getstatf(STAT_GAMESTARTTIME)) {
1864                         //while restart is still active, show 00:00
1865                         timer = seconds_tostring(0);
1866                 } else {
1867                         elapsedTime = floor(time - getstatf(STAT_GAMESTARTTIME)); //127
1868                         timer = seconds_tostring(elapsedTime);
1869                 }
1870         } else {
1871                 timer = seconds_tostring(timeleft);
1872         }
1873
1874         drawstring_aspect(pos, timer, mySize, timer_color, panel_fg_alpha, DRAWFLAG_NORMAL);
1875
1876         draw_endBoldFont();
1877 }
1878
1879 // Radar (#6)
1880 //
1881
1882 float HUD_Radar_Clickable()
1883 {
1884         return hud_panel_radar_mouse && !hud_panel_radar_temp_hidden;
1885 }
1886
1887 void HUD_Radar_Show_Maximized(bool doshow,float clickable)
1888 {
1889         hud_panel_radar_maximized = doshow;
1890         hud_panel_radar_temp_hidden = 0;
1891
1892         if ( doshow )
1893         {
1894                 if (clickable)
1895                 {
1896                         if(autocvar_hud_cursormode)
1897                                 setcursormode(1);
1898                         hud_panel_radar_mouse = 1;
1899                 }
1900         }
1901         else if ( hud_panel_radar_mouse )
1902         {
1903                 hud_panel_radar_mouse = 0;
1904                 mouseClicked = 0;
1905                 if(autocvar_hud_cursormode)
1906                 if(!mv_active)
1907                         setcursormode(0);
1908         }
1909 }
1910 void HUD_Radar_Hide_Maximized()
1911 {
1912         HUD_Radar_Show_Maximized(false,false);
1913 }
1914
1915
1916 float HUD_Radar_InputEvent(float bInputType, float nPrimary, float nSecondary)
1917 {
1918         if(!hud_panel_radar_maximized || !hud_panel_radar_mouse ||
1919                 autocvar__hud_configure || mv_active)
1920                 return false;
1921
1922         if(bInputType == 3)
1923         {
1924                 mousepos_x = nPrimary;
1925                 mousepos_y = nSecondary;
1926                 return true;
1927         }
1928
1929         if(nPrimary == K_MOUSE1)
1930         {
1931                 if(bInputType == 0) // key pressed
1932                         mouseClicked |= S_MOUSE1;
1933                 else if(bInputType == 1) // key released
1934                         mouseClicked -= (mouseClicked & S_MOUSE1);
1935         }
1936         else if(nPrimary == K_MOUSE2)
1937         {
1938                 if(bInputType == 0) // key pressed
1939                         mouseClicked |= S_MOUSE2;
1940                 else if(bInputType == 1) // key released
1941                         mouseClicked -= (mouseClicked & S_MOUSE2);
1942         }
1943         else if ( nPrimary == K_ESCAPE && bInputType == 0 )
1944         {
1945                 HUD_Radar_Hide_Maximized();
1946         }
1947         else
1948         {
1949                 // allow console/use binds to work without hiding the map
1950                 string con_keys;
1951                 float keys;
1952                 float i;
1953                 con_keys = strcat(findkeysforcommand("toggleconsole", 0)," ",findkeysforcommand("+use", 0)) ;
1954                 keys = tokenize(con_keys); // findkeysforcommand returns data for this
1955                 for (i = 0; i < keys; ++i)
1956                 {
1957                         if(nPrimary == stof(argv(i)))
1958                                 return false;
1959                 }
1960
1961                 if ( getstati(STAT_HEALTH) <= 0 )
1962                 {
1963                         // Show scoreboard
1964                         if ( bInputType < 2 )
1965                         {
1966                                 con_keys = findkeysforcommand("+showscores", 0);
1967                                 keys = tokenize(con_keys);
1968                                 for (i = 0; i < keys; ++i)
1969                                 {
1970                                         if ( nPrimary == stof(argv(i)) )
1971                                         {
1972                                                 hud_panel_radar_temp_hidden = bInputType == 0;
1973                                                 return false;
1974                                         }
1975                                 }
1976                         }
1977                 }
1978                 else if ( bInputType == 0 )
1979                         HUD_Radar_Hide_Maximized();
1980
1981                 return false;
1982         }
1983
1984         return true;
1985 }
1986
1987 void HUD_Radar_Mouse()
1988 {
1989         if ( !hud_panel_radar_mouse ) return;
1990         if(mv_active) return;
1991
1992         if ( intermission )
1993         {
1994                 HUD_Radar_Hide_Maximized();
1995                 return;
1996         }
1997
1998         if(mouseClicked & S_MOUSE2)
1999         {
2000                 HUD_Radar_Hide_Maximized();
2001                 return;
2002         }
2003
2004         if(!autocvar_hud_cursormode)
2005         {
2006                 mousepos = mousepos + getmousepos() * autocvar_menu_mouse_speed;
2007
2008                 mousepos_x = bound(0, mousepos_x, vid_conwidth);
2009                 mousepos_y = bound(0, mousepos_y, vid_conheight);
2010         }
2011
2012         HUD_Panel_UpdateCvars();
2013
2014
2015         panel_size = autocvar_hud_panel_radar_maximized_size;
2016         panel_size_x = bound(0.2, panel_size_x, 1) * vid_conwidth;
2017         panel_size_y = bound(0.2, panel_size_y, 1) * vid_conheight;
2018         panel_pos_x = (vid_conwidth - panel_size_x) / 2;
2019         panel_pos_y = (vid_conheight - panel_size_y) / 2;
2020
2021         if(mouseClicked & S_MOUSE1)
2022         {
2023                 // click outside
2024                 if ( mousepos_x < panel_pos_x || mousepos_x > panel_pos_x + panel_size_x ||
2025                          mousepos_y < panel_pos_y || mousepos_y > panel_pos_y + panel_size_y )
2026                 {
2027                         HUD_Radar_Hide_Maximized();
2028                         return;
2029                 }
2030                 vector pos = teamradar_texcoord_to_3dcoord(teamradar_2dcoord_to_texcoord(mousepos),view_origin_z);
2031                 localcmd(sprintf("cmd ons_spawn %f %f %f",pos_x,pos_y,pos_z));
2032
2033                 HUD_Radar_Hide_Maximized();
2034                 return;
2035         }
2036
2037
2038         const vector cursor_size = '32 32 0';
2039         drawpic(mousepos-'8 4 0', strcat("gfx/menu/", autocvar_menu_skin, "/cursor.tga"), cursor_size, '1 1 1', 0.8, DRAWFLAG_NORMAL);
2040 }
2041
2042 void HUD_Radar(void)
2043 {
2044         if (!autocvar__hud_configure)
2045         {
2046                 if (hud_panel_radar_maximized)
2047                 {
2048                         if (!hud_draw_maximized) return;
2049                 }
2050                 else
2051                 {
2052                         if (autocvar_hud_panel_radar == 0) return;
2053                         if (autocvar_hud_panel_radar != 2 && !teamplay) return;
2054                         if(radar_panel_modified)
2055                         {
2056                                 panel.update_time = time; // forces reload of panel attributes
2057                                 radar_panel_modified = false;
2058                         }
2059                 }
2060         }
2061
2062         if ( hud_panel_radar_temp_hidden )
2063                 return;
2064
2065         HUD_Panel_UpdateCvars();
2066
2067         float f = 0;
2068
2069         if (hud_panel_radar_maximized && !autocvar__hud_configure)
2070         {
2071                 panel_size = autocvar_hud_panel_radar_maximized_size;
2072                 panel_size.x = bound(0.2, panel_size.x, 1) * vid_conwidth;
2073                 panel_size.y = bound(0.2, panel_size.y, 1) * vid_conheight;
2074                 panel_pos.x = (vid_conwidth - panel_size.x) / 2;
2075                 panel_pos.y = (vid_conheight - panel_size.y) / 2;
2076
2077                 string panel_bg;
2078                 panel_bg = strcat(hud_skin_path, "/border_default"); // always use the default border when maximized
2079                 if(precache_pic(panel_bg) == "")
2080                         panel_bg = "gfx/hud/default/border_default"; // fallback
2081                 if(!radar_panel_modified && panel_bg != panel.current_panel_bg)
2082                         radar_panel_modified = true;
2083                 if(panel.current_panel_bg)
2084                         strunzone(panel.current_panel_bg);
2085                 panel.current_panel_bg = strzone(panel_bg);
2086
2087                 switch(hud_panel_radar_maximized_zoommode)
2088                 {
2089                         default:
2090                         case 0:
2091                                 f = current_zoomfraction;
2092                                 break;
2093                         case 1:
2094                                 f = 1 - current_zoomfraction;
2095                                 break;
2096                         case 2:
2097                                 f = 0;
2098                                 break;
2099                         case 3:
2100                                 f = 1;
2101                                 break;
2102                 }
2103
2104                 switch(hud_panel_radar_maximized_rotation)
2105                 {
2106                         case 0:
2107                                 teamradar_angle = view_angles.y - 90;
2108                                 break;
2109                         default:
2110                                 teamradar_angle = 90 * hud_panel_radar_maximized_rotation;
2111                                 break;
2112                 }
2113         }
2114         if (!hud_panel_radar_maximized && !autocvar__hud_configure)
2115         {
2116                 switch(hud_panel_radar_zoommode)
2117                 {
2118                         default:
2119                         case 0:
2120                                 f = current_zoomfraction;
2121                                 break;
2122                         case 1:
2123                                 f = 1 - current_zoomfraction;
2124                                 break;
2125                         case 2:
2126                                 f = 0;
2127                                 break;
2128                         case 3:
2129                                 f = 1;
2130                                 break;
2131                 }
2132
2133                 switch(hud_panel_radar_rotation)
2134                 {
2135                         case 0:
2136                                 teamradar_angle = view_angles.y - 90;
2137                                 break;
2138                         default:
2139                                 teamradar_angle = 90 * hud_panel_radar_rotation;
2140                                 break;
2141                 }
2142         }
2143
2144         vector pos, mySize;
2145         pos = panel_pos;
2146         mySize = panel_size;
2147
2148         HUD_Panel_DrawBg(1);
2149         if(panel_bg_padding)
2150         {
2151                 pos += '1 1 0' * panel_bg_padding;
2152                 mySize -= '2 2 0' * panel_bg_padding;
2153         }
2154
2155         int color2;
2156         entity tm;
2157         float scale2d, normalsize, bigsize;
2158
2159         teamradar_origin2d = pos + 0.5 * mySize;
2160         teamradar_size2d = mySize;
2161
2162         if(minimapname == "")
2163                 return;
2164
2165         teamradar_loadcvars();
2166
2167         scale2d = vlen_maxnorm2d(mi_picmax - mi_picmin);
2168         teamradar_size2d = mySize;
2169
2170         teamradar_extraclip_mins = teamradar_extraclip_maxs = '0 0 0'; // we always center
2171
2172         // pixels per world qu to match the teamradar_size2d_x range in the longest dimension
2173         if((hud_panel_radar_rotation == 0 && !hud_panel_radar_maximized) || (hud_panel_radar_maximized_rotation == 0 && hud_panel_radar_maximized))
2174         {
2175                 // max-min distance must fit the radar in any rotation
2176                 bigsize = vlen_minnorm2d(teamradar_size2d) * scale2d / (1.05 * vlen2d(mi_scale));
2177         }
2178         else
2179         {
2180                 vector c0, c1, c2, c3, span;
2181                 c0 = rotate(mi_min, teamradar_angle * DEG2RAD);
2182                 c1 = rotate(mi_max, teamradar_angle * DEG2RAD);
2183                 c2 = rotate('1 0 0' * mi_min.x + '0 1 0' * mi_max.y, teamradar_angle * DEG2RAD);
2184                 c3 = rotate('1 0 0' * mi_max.x + '0 1 0' * mi_min.y, teamradar_angle * DEG2RAD);
2185                 span = '0 0 0';
2186                 span.x = max(c0_x, c1_x, c2_x, c3_x) - min(c0_x, c1_x, c2_x, c3_x);
2187                 span.y = max(c0_y, c1_y, c2_y, c3_y) - min(c0_y, c1_y, c2_y, c3_y);
2188
2189                 // max-min distance must fit the radar in x=x, y=y
2190                 bigsize = min(
2191                         teamradar_size2d.x * scale2d / (1.05 * span.x),
2192                         teamradar_size2d.y * scale2d / (1.05 * span.y)
2193                 );
2194         }
2195
2196         normalsize = vlen_maxnorm2d(teamradar_size2d) * scale2d / hud_panel_radar_scale;
2197         if(bigsize > normalsize)
2198                 normalsize = bigsize;
2199
2200         teamradar_size =
2201                   f * bigsize
2202                 + (1 - f) * normalsize;
2203         teamradar_origin3d_in_texcoord = teamradar_3dcoord_to_texcoord(
2204                   f * mi_center
2205                 + (1 - f) * view_origin);
2206
2207         drawsetcliparea(
2208                 pos.x,
2209                 pos.y,
2210                 mySize.x,
2211                 mySize.y
2212         );
2213
2214         draw_teamradar_background(hud_panel_radar_foreground_alpha);
2215
2216         for(tm = world; (tm = find(tm, classname, "radarlink")); )
2217                 draw_teamradar_link(tm.origin, tm.velocity, tm.team);
2218
2219         vector coord;
2220         vector brightcolor;
2221         for(tm = world; (tm = findflags(tm, teamradar_icon, 0xFFFFFF)); )
2222         {
2223                 if ( hud_panel_radar_mouse )
2224                 if ( tm.health > 0 )
2225                 if ( tm.team == myteam+1 )
2226                 {
2227                         coord = teamradar_texcoord_to_2dcoord(teamradar_3dcoord_to_texcoord(tm.origin));
2228                         if ( vlen(mousepos-coord) < 8 )
2229                         {
2230                                 brightcolor_x = min(1,tm.teamradar_color_x*1.5);
2231                                 brightcolor_y = min(1,tm.teamradar_color_y*1.5);
2232                                 brightcolor_z = min(1,tm.teamradar_color_z*1.5);
2233                                 drawpic(coord - '8 8 0', "gfx/teamradar_icon_glow", '16 16 0', brightcolor, panel_fg_alpha, 0);
2234                         }
2235                 }
2236                 entity icon = RadarIcons[tm.teamradar_icon];
2237                 draw_teamradar_icon(tm.origin, icon, tm, spritelookupcolor(tm, icon.netname, tm.teamradar_color), panel_fg_alpha);
2238         }
2239         for(tm = world; (tm = find(tm, classname, "entcs_receiver")); )
2240         {
2241                 color2 = GetPlayerColor(tm.sv_entnum);
2242                 //if(color == NUM_SPECTATOR || color == color2)
2243                         draw_teamradar_player(tm.origin, tm.angles, Team_ColorRGB(color2));
2244         }
2245         draw_teamradar_player(view_origin, view_angles, '1 1 1');
2246
2247         drawresetcliparea();
2248
2249         if ( hud_panel_radar_mouse )
2250         {
2251                 string message = "Click to select teleport destination";
2252
2253                 if ( getstati(STAT_HEALTH) <= 0 )
2254                 {
2255                         message = "Click to select spawn location";
2256                 }
2257
2258                 drawcolorcodedstring(pos + '0.5 0 0' * (mySize_x - stringwidth(message, true, hud_fontsize)) - '0 1 0' * hud_fontsize_y * 2,
2259                                                          message, hud_fontsize, hud_panel_radar_foreground_alpha, DRAWFLAG_NORMAL);
2260
2261                 hud_panel_radar_bottom = pos_y + mySize_y + hud_fontsize_y;
2262         }
2263 }
2264
2265 // Score (#7)
2266 //
2267 void HUD_UpdatePlayerTeams();
2268 void HUD_Score_Rankings(vector pos, vector mySize, entity me)
2269 {
2270         float score;
2271         entity tm = world, pl;
2272         int SCOREPANEL_MAX_ENTRIES = 6;
2273         float SCOREPANEL_ASPECTRATIO = 2;
2274         int entries = bound(1, floor(SCOREPANEL_MAX_ENTRIES * mySize.y/mySize.x * SCOREPANEL_ASPECTRATIO), SCOREPANEL_MAX_ENTRIES);
2275         vector fontsize = '1 1 0' * (mySize.y/entries);
2276
2277         vector rgb, score_color;
2278         rgb = '1 1 1';
2279         score_color = '1 1 1';
2280
2281         float name_size = mySize.x*0.75;
2282         float spacing_size = mySize.x*0.04;
2283         const float highlight_alpha = 0.2;
2284         int i = 0, first_pl = 0;
2285         bool me_printed = false;
2286         string s;
2287         if (autocvar__hud_configure)
2288         {
2289                 float players_per_team = 0;
2290                 if (team_count)
2291                 {
2292                         // show team scores in the first line
2293                         float score_size = mySize.x / team_count;
2294                         players_per_team = max(2, ceil((entries - 1) / team_count));
2295                         for(i=0; i<team_count; ++i) {
2296                                 if (i == floor((entries - 2) / players_per_team) || (entries == 1 && i == 0))
2297                                         HUD_Panel_DrawHighlight(pos + eX * score_size * i, eX * score_size + eY * fontsize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2298                                 drawstring_aspect(pos + eX * score_size * i, ftos(175 - 23*i), eX * score_size + eY * fontsize.y, Team_ColorRGB(ColorByTeam(i)) * 0.8, panel_fg_alpha, DRAWFLAG_NORMAL);
2299                         }
2300                         first_pl = 1;
2301                         pos.y += fontsize.y;
2302                 }
2303                 score = 10 + SCOREPANEL_MAX_ENTRIES * 3;
2304                 for (i=first_pl; i<entries; ++i)
2305                 {
2306                         //simulate my score is lower than all displayed players,
2307                         //so that I don't appear at all showing pure rankings.
2308                         //This is to better show the difference between the 2 ranking views
2309                         if (i == entries-1 && autocvar_hud_panel_score_rankings == 1)
2310                         {
2311                                 rgb = '1 1 0';
2312                                 drawfill(pos, eX * mySize.x + eY * fontsize.y, rgb, highlight_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
2313                                 s = GetPlayerName(player_localnum);
2314                                 score = 7;
2315                         }
2316                         else
2317                         {
2318                                 s = sprintf(_("Player %d"), i + 1 - first_pl);
2319                                 score -= 3;
2320                         }
2321
2322                         if (team_count)
2323                                 score_color = Team_ColorRGB(ColorByTeam(floor((i - first_pl) / players_per_team))) * 0.8;
2324                         s = textShortenToWidth(s, name_size, fontsize, stringwidth_colors);
2325                         drawcolorcodedstring(pos + eX * (name_size - stringwidth(s, true, fontsize)), s, fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2326                         drawstring(pos + eX * (name_size + spacing_size), ftos(score), fontsize, score_color, panel_fg_alpha, DRAWFLAG_NORMAL);
2327                         pos.y += fontsize.y;
2328                 }
2329                 return;
2330         }
2331
2332         if (!scoreboard_fade_alpha) // the scoreboard too calls HUD_UpdatePlayerTeams
2333                 HUD_UpdatePlayerTeams();
2334         if (team_count)
2335         {
2336                 // show team scores in the first line
2337                 float score_size = mySize.x / team_count;
2338                 for(tm = teams.sort_next; tm; tm = tm.sort_next) {
2339                         if(tm.team == NUM_SPECTATOR)
2340                                 continue;
2341                         if (tm.team == myteam)
2342                                 drawfill(pos + eX * score_size * i, eX * score_size + eY * fontsize.y, '1 1 1', highlight_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
2343                         drawstring_aspect(pos + eX * score_size * i, ftos(tm.(teamscores[ts_primary])), eX * score_size + eY * fontsize.y, Team_ColorRGB(tm.team) * 0.8, panel_fg_alpha, DRAWFLAG_NORMAL);
2344                         ++i;
2345                 }
2346                 first_pl = 1;
2347                 pos.y += fontsize.y;
2348                 tm = teams.sort_next;
2349         }
2350         i = first_pl;
2351
2352         do
2353         for (pl = players.sort_next; pl && i<entries; pl = pl.sort_next)
2354         {
2355                 if ((team_count && pl.team != tm.team) || pl.team == NUM_SPECTATOR)
2356                         continue;
2357
2358                 if (i == entries-1 && !me_printed && pl != me)
2359                 if (autocvar_hud_panel_score_rankings == 1 && spectatee_status != -1)
2360                 {
2361                         for (pl = me.sort_next; pl; pl = pl.sort_next)
2362                                 if (pl.team != NUM_SPECTATOR)
2363                                         break;
2364
2365                         if (pl)
2366                                 rgb = '1 1 0'; //not last but not among the leading players: yellow
2367                         else
2368                                 rgb = '1 0 0'; //last: red
2369                         pl = me;
2370                 }
2371
2372                 if (pl == me)
2373                 {
2374                         if (i == first_pl)
2375                                 rgb = '0 1 0'; //first: green
2376                         me_printed = true;
2377                         drawfill(pos, eX * mySize.x + eY * fontsize.y, rgb, highlight_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
2378                 }
2379                 if (team_count)
2380                         score_color = Team_ColorRGB(pl.team) * 0.8;
2381                 s = textShortenToWidth(GetPlayerName(pl.sv_entnum), name_size, fontsize, stringwidth_colors);
2382                 drawcolorcodedstring(pos + eX * (name_size - stringwidth(s, true, fontsize)), s, fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2383                 drawstring(pos + eX * (name_size + spacing_size), ftos(pl.(scores[ps_primary])), fontsize, score_color, panel_fg_alpha, DRAWFLAG_NORMAL);
2384                 pos.y += fontsize.y;
2385                 ++i;
2386         }
2387         while (i<entries && team_count && (tm = tm.sort_next) && (tm.team != NUM_SPECTATOR || (tm = tm.sort_next)));
2388 }
2389
2390 void HUD_Score(void)
2391 {
2392         if(!autocvar__hud_configure)
2393         {
2394                 if(!autocvar_hud_panel_score) return;
2395                 if(spectatee_status == -1 && (gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
2396         }
2397
2398         HUD_Panel_UpdateCvars();
2399         vector pos, mySize;
2400         pos = panel_pos;
2401         mySize = panel_size;
2402
2403         HUD_Panel_DrawBg(1);
2404         if(panel_bg_padding)
2405         {
2406                 pos += '1 1 0' * panel_bg_padding;
2407                 mySize -= '2 2 0' * panel_bg_padding;
2408         }
2409
2410         float score, distribution = 0;
2411         string sign;
2412         vector distribution_color;
2413         entity tm, pl, me;
2414
2415         me = playerslots[current_player];
2416
2417         if((scores_flags[ps_primary] & SFL_TIME) && !teamplay) { // race/cts record display on HUD
2418                 string timer, distrtimer;
2419
2420                 pl = players.sort_next;
2421                 if(pl == me)
2422                         pl = pl.sort_next;
2423                 if(scores_flags[ps_primary] & SFL_ZERO_IS_WORST)
2424                         if(pl.scores[ps_primary] == 0)
2425                                 pl = world;
2426
2427                 score = me.(scores[ps_primary]);
2428                 timer = TIME_ENCODED_TOSTRING(score);
2429
2430                 draw_beginBoldFont();
2431                 if (pl && ((!(scores_flags[ps_primary] & SFL_ZERO_IS_WORST)) || score)) {
2432                         // distribution display
2433                         distribution = me.(scores[ps_primary]) - pl.(scores[ps_primary]);
2434
2435                         distrtimer = ftos_decimals(fabs(distribution/pow(10, TIME_DECIMALS)), TIME_DECIMALS);
2436
2437                         if (distribution <= 0) {
2438                                 distribution_color = '0 1 0';
2439                                 sign = "-";
2440                         }
2441                         else {
2442                                 distribution_color = '1 0 0';
2443                                 sign = "+";
2444                         }
2445                         drawstring_aspect(pos + eX * 0.75 * mySize.x, strcat(sign, distrtimer), eX * 0.25 * mySize.x + eY * (1/3) * mySize.y, distribution_color, panel_fg_alpha, DRAWFLAG_NORMAL);
2446                 }
2447                 // race record display
2448                 if (distribution <= 0)
2449                         HUD_Panel_DrawHighlight(pos, eX * 0.75 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2450                 drawstring_aspect(pos, timer, eX * 0.75 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2451                 draw_endBoldFont();
2452         } else if (!teamplay) { // non-teamgames
2453                 if ((spectatee_status == -1 && !autocvar__hud_configure) || autocvar_hud_panel_score_rankings)
2454                 {
2455                         HUD_Score_Rankings(pos, mySize, me);
2456                         return;
2457                 }
2458                 // me vector := [team/connected frags id]
2459                 pl = players.sort_next;
2460                 if(pl == me)
2461                         pl = pl.sort_next;
2462
2463                 if(autocvar__hud_configure)
2464                         distribution = 42;
2465                 else if(pl)
2466                         distribution = me.(scores[ps_primary]) - pl.(scores[ps_primary]);
2467                 else
2468                         distribution = 0;
2469
2470                 score = me.(scores[ps_primary]);
2471                 if(autocvar__hud_configure)
2472                         score = 123;
2473
2474                 if(distribution >= 5)
2475                         distribution_color = eY;
2476                 else if(distribution >= 0)
2477                         distribution_color = '1 1 1';
2478                 else if(distribution >= -5)
2479                         distribution_color = '1 1 0';
2480                 else
2481                         distribution_color = eX;
2482
2483                 string distribution_str;
2484                 distribution_str = ftos(distribution);
2485                 draw_beginBoldFont();
2486                 if (distribution >= 0)
2487                 {
2488                         if (distribution > 0)
2489                                 distribution_str = strcat("+", distribution_str);
2490                         HUD_Panel_DrawHighlight(pos, eX * 0.75 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2491                 }
2492                 drawstring_aspect(pos, ftos(score), eX * 0.75 * mySize.x + eY * mySize.y, distribution_color, panel_fg_alpha, DRAWFLAG_NORMAL);
2493                 drawstring_aspect(pos + eX * 0.75 * mySize.x, distribution_str, eX * 0.25 * mySize.x + eY * (1/3) * mySize.y, distribution_color, panel_fg_alpha, DRAWFLAG_NORMAL);
2494                 draw_endBoldFont();
2495         } else { // teamgames
2496                 float row, column, rows = 0, columns = 0;
2497                 vector offset = '0 0 0';
2498                 vector score_pos, score_size; //for scores other than myteam
2499                 if(autocvar_hud_panel_score_rankings)
2500                 {
2501                         HUD_Score_Rankings(pos, mySize, me);
2502                         return;
2503                 }
2504                 if(spectatee_status == -1)
2505                 {
2506                         rows = HUD_GetRowCount(team_count, mySize, 3);
2507                         columns = ceil(team_count/rows);
2508                         score_size = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
2509
2510                         float newSize;
2511                         if(score_size.x/score_size.y > 3)
2512                         {
2513                                 newSize = 3 * score_size.y;
2514                                 offset.x = score_size.x - newSize;
2515                                 pos.x += offset.x/2;
2516                                 score_size.x = newSize;
2517                         }
2518                         else
2519                         {
2520                                 newSize = 1/3 * score_size.x;
2521                                 offset.y = score_size.y - newSize;
2522                                 pos.y += offset.y/2;
2523                                 score_size.y = newSize;
2524                         }
2525                 }
2526                 else
2527                         score_size = eX * mySize.x*(1/4) + eY * mySize.y*(1/3);
2528
2529                 float max_fragcount;
2530                 max_fragcount = -99;
2531                 draw_beginBoldFont();
2532                 row = column = 0;
2533                 for(tm = teams.sort_next; tm; tm = tm.sort_next) {
2534                         if(tm.team == NUM_SPECTATOR)
2535                                 continue;
2536                         score = tm.(teamscores[ts_primary]);
2537                         if(autocvar__hud_configure)
2538                                 score = 123;
2539
2540                         if (score > max_fragcount)
2541                                 max_fragcount = score;
2542
2543                         if (spectatee_status == -1)
2544                         {
2545                                 score_pos = pos + eX * column * (score_size.x + offset.x) + eY * row * (score_size.y + offset.y);
2546                                 if (max_fragcount == score)
2547                                         HUD_Panel_DrawHighlight(score_pos, score_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2548                                 drawstring_aspect(score_pos, ftos(score), score_size, Team_ColorRGB(tm.team) * 0.8, panel_fg_alpha, DRAWFLAG_NORMAL);
2549                                 ++row;
2550                                 if(row >= rows)
2551                                 {
2552                                         row = 0;
2553                                         ++column;
2554                                 }
2555                         }
2556                         else if(tm.team == myteam) {
2557                                 if (max_fragcount == score)
2558                                         HUD_Panel_DrawHighlight(pos, eX * 0.75 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2559                                 drawstring_aspect(pos, ftos(score), eX * 0.75 * mySize.x + eY * mySize.y, Team_ColorRGB(tm.team) * 0.8, panel_fg_alpha, DRAWFLAG_NORMAL);
2560                         } else {
2561                                 if (max_fragcount == score)
2562                                         HUD_Panel_DrawHighlight(pos + eX * 0.75 * mySize.x + eY * (1/3) * rows * mySize.y, score_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2563                                 drawstring_aspect(pos + eX * 0.75 * mySize.x + eY * (1/3) * rows * mySize.y, ftos(score), score_size, Team_ColorRGB(tm.team) * 0.8, panel_fg_alpha, DRAWFLAG_NORMAL);
2564                                 ++rows;
2565                         }
2566                 }
2567                 draw_endBoldFont();
2568         }
2569 }
2570
2571 // Race timer (#8)
2572 //
2573 void HUD_RaceTimer (void)
2574 {
2575         if(!autocvar__hud_configure)
2576         {
2577                 if(!autocvar_hud_panel_racetimer) return;
2578                 if(!(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
2579                 if(spectatee_status == -1) return;
2580         }
2581
2582         HUD_Panel_UpdateCvars();
2583
2584         vector pos, mySize;
2585         pos = panel_pos;
2586         mySize = panel_size;
2587
2588         HUD_Panel_DrawBg(1);
2589         if(panel_bg_padding)
2590         {
2591                 pos += '1 1 0' * panel_bg_padding;
2592                 mySize -= '2 2 0' * panel_bg_padding;
2593         }
2594
2595         // always force 4:1 aspect
2596         vector newSize = '0 0 0';
2597         if(mySize.x/mySize.y > 4)
2598         {
2599                 newSize.x = 4 * mySize.y;
2600                 newSize.y = mySize.y;
2601
2602                 pos.x = pos.x + (mySize.x - newSize.x) / 2;
2603         }
2604         else
2605         {
2606                 newSize.y = 1/4 * mySize.x;
2607                 newSize.x = mySize.x;
2608
2609                 pos.y = pos.y + (mySize.y - newSize.y) / 2;
2610         }
2611         mySize = newSize;
2612
2613         float a, t;
2614         string s, forcetime;
2615
2616         if(autocvar__hud_configure)
2617         {
2618                 s = "0:13:37";
2619                 draw_beginBoldFont();
2620                 drawstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, false, '0.60 0.60 0' * mySize.y), s, '0.60 0.60 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2621                 draw_endBoldFont();
2622                 s = _("^1Intermediate 1 (+15.42)");
2623                 drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.20 * mySize.y) + eY * 0.60 * mySize.y, s, '1 1 0' * 0.20 * mySize.y, panel_fg_alpha, DRAWFLAG_NORMAL);
2624                 s = sprintf(_("^1PENALTY: %.1f (%s)"), 2, "missing a checkpoint");
2625                 drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.20 * mySize.y) + eY * 0.80 * mySize.y, s, '1 1 0' * 0.20 * mySize.y, panel_fg_alpha, DRAWFLAG_NORMAL);
2626         }
2627         else if(race_checkpointtime)
2628         {
2629                 a = bound(0, 2 - (time - race_checkpointtime), 1);
2630                 s = "";
2631                 forcetime = "";
2632                 if(a > 0) // just hit a checkpoint?
2633                 {
2634                         if(race_checkpoint != 254)
2635                         {
2636                                 if(race_time && race_previousbesttime)
2637                                         s = MakeRaceString(race_checkpoint, TIME_DECODE(race_time) - TIME_DECODE(race_previousbesttime), 0, 0, race_previousbestname);
2638                                 else
2639                                         s = MakeRaceString(race_checkpoint, 0, -1, 0, race_previousbestname);
2640                                 if(race_time)
2641                                         forcetime = TIME_ENCODED_TOSTRING(race_time);
2642                         }
2643                 }
2644                 else
2645                 {
2646                         if(race_laptime && race_nextbesttime && race_nextcheckpoint != 254)
2647                         {
2648                                 a = bound(0, 2 - ((race_laptime + TIME_DECODE(race_nextbesttime)) - (time + TIME_DECODE(race_penaltyaccumulator))), 1);
2649                                 if(a > 0) // next one?
2650                                 {
2651                                         s = MakeRaceString(race_nextcheckpoint, (time + TIME_DECODE(race_penaltyaccumulator)) - race_laptime, TIME_DECODE(race_nextbesttime), 0, race_nextbestname);
2652                                 }
2653                         }
2654                 }
2655
2656                 if(s != "" && a > 0)
2657                 {
2658                         drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.2 * mySize.y) + eY * 0.6 * mySize.y, s, '1 1 0' * 0.2 * mySize.y, panel_fg_alpha * a, DRAWFLAG_NORMAL);
2659                 }
2660
2661                 if(race_penaltytime)
2662                 {
2663                         a = bound(0, 2 - (time - race_penaltyeventtime), 1);
2664                         if(a > 0)
2665                         {
2666                                 s = sprintf(_("^1PENALTY: %.1f (%s)"), race_penaltytime * 0.1, race_penaltyreason);
2667                                 drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.2 * mySize.y) + eY * 0.8 * mySize.y, s, '1 1 0' * 0.2 * mySize.y, panel_fg_alpha * a, DRAWFLAG_NORMAL);
2668                         }
2669                 }
2670
2671                 draw_beginBoldFont();
2672
2673                 if(forcetime != "")
2674                 {
2675                         a = bound(0, (time - race_checkpointtime) / 0.5, 1);
2676                         drawstring_expanding(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(forcetime, false, '1 1 0' * 0.6 * mySize.y), forcetime, '1 1 0' * 0.6 * mySize.y, '1 1 1', panel_fg_alpha, 0, a);
2677                 }
2678                 else
2679                         a = 1;
2680
2681                 if(race_laptime && race_checkpoint != 255)
2682                 {
2683                         s = TIME_ENCODED_TOSTRING(TIME_ENCODE(time + TIME_DECODE(race_penaltyaccumulator) - race_laptime));
2684                         drawstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, false, '0.6 0.6 0' * mySize.y), s, '0.6 0.6 0' * mySize.y, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
2685                 }
2686
2687                 draw_endBoldFont();
2688         }
2689         else
2690         {
2691                 if(race_mycheckpointtime)
2692                 {
2693                         a = bound(0, 2 - (time - race_mycheckpointtime), 1);
2694                         s = MakeRaceString(race_mycheckpoint, TIME_DECODE(race_mycheckpointdelta), -(race_mycheckpointenemy == ""), race_mycheckpointlapsdelta, race_mycheckpointenemy);
2695                         drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.2 * mySize.y) + eY * 0.6 * mySize.y, s, '1 1 0' * 0.2 * mySize.y, panel_fg_alpha * a, DRAWFLAG_NORMAL);
2696                 }
2697                 if(race_othercheckpointtime && race_othercheckpointenemy != "")
2698                 {
2699                         a = bound(0, 2 - (time - race_othercheckpointtime), 1);
2700                         s = MakeRaceString(race_othercheckpoint, -TIME_DECODE(race_othercheckpointdelta), -(race_othercheckpointenemy == ""), race_othercheckpointlapsdelta, race_othercheckpointenemy);
2701                         drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.2 * mySize.y) + eY * 0.6 * mySize.y, s, '1 1 0' * 0.2 * mySize.y, panel_fg_alpha * a, DRAWFLAG_NORMAL);
2702                 }
2703
2704                 if(race_penaltytime && !race_penaltyaccumulator)
2705                 {
2706                         t = race_penaltytime * 0.1 + race_penaltyeventtime;
2707                         a = bound(0, (1 + t - time), 1);
2708                         if(a > 0)
2709                         {
2710                                 if(time < t)
2711                                         s = sprintf(_("^1PENALTY: %.1f (%s)"), (t - time) * 0.1, race_penaltyreason);
2712                                 else
2713                                         s = sprintf(_("^2PENALTY: %.1f (%s)"), 0, race_penaltyreason);
2714                                 drawcolorcodedstring(pos + eX * 0.5 * mySize.x - '0.5 0 0' * stringwidth(s, true, '1 1 0' * 0.2 * mySize.y) + eY * 0.6 * mySize.y, s, '1 1 0' * 0.2 * mySize.y, panel_fg_alpha * a, DRAWFLAG_NORMAL);
2715                         }
2716                 }
2717         }
2718 }
2719
2720 // Vote window (#9)
2721 //
2722
2723 void HUD_Vote(void)
2724 {
2725         if(autocvar_cl_allow_uid2name == -1 && (gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (serverflags & SERVERFLAG_PLAYERSTATS)))
2726         {
2727                 vote_active = 1;
2728                 if (autocvar__hud_configure)
2729                 {
2730                         vote_yescount = 0;
2731                         vote_nocount = 0;
2732                         LOG_INFO(_("^1You must answer before entering hud configure mode\n"));
2733                         cvar_set("_hud_configure", "0");
2734                 }
2735                 if(vote_called_vote)
2736                         strunzone(vote_called_vote);
2737                 vote_called_vote = strzone(_("^2Name ^7instead of \"^1Anonymous player^7\" in stats"));
2738                 uid2name_dialog = 1;
2739         }
2740
2741         if(!autocvar__hud_configure)
2742         {
2743                 if(!autocvar_hud_panel_vote) return;
2744
2745                 panel_fg_alpha = autocvar_hud_panel_fg_alpha;
2746                 panel_bg_alpha_str = autocvar_hud_panel_vote_bg_alpha;
2747
2748                 if(panel_bg_alpha_str == "") {
2749                         panel_bg_alpha_str = ftos(autocvar_hud_panel_bg_alpha);
2750                 }
2751                 panel_bg_alpha = stof(panel_bg_alpha_str);
2752         }
2753         else
2754         {
2755                 vote_yescount = 3;
2756                 vote_nocount = 2;
2757                 vote_needed = 4;
2758         }
2759
2760         string s;
2761         float a;
2762         if(vote_active != vote_prev) {
2763                 vote_change = time;
2764                 vote_prev = vote_active;
2765         }
2766
2767         if(vote_active || autocvar__hud_configure)
2768                 vote_alpha = bound(0, (time - vote_change) * 2, 1);
2769         else
2770                 vote_alpha = bound(0, 1 - (time - vote_change) * 2, 1);
2771
2772         if(!vote_alpha)
2773                 return;
2774
2775         HUD_Panel_UpdateCvars();
2776
2777         if(uid2name_dialog)
2778         {
2779                 panel_pos = eX * 0.3 * vid_conwidth + eY * 0.1 * vid_conheight;
2780                 panel_size = eX * 0.4 * vid_conwidth + eY * 0.3 * vid_conheight;
2781         }
2782
2783     // these must be below above block
2784         vector pos, mySize;
2785         pos = panel_pos;
2786         mySize = panel_size;
2787
2788         a = vote_alpha * (vote_highlighted ? autocvar_hud_panel_vote_alreadyvoted_alpha : 1);
2789         HUD_Panel_DrawBg(a);
2790         a = panel_fg_alpha * a;
2791
2792         if(panel_bg_padding)
2793         {
2794                 pos += '1 1 0' * panel_bg_padding;
2795                 mySize -= '2 2 0' * panel_bg_padding;
2796         }
2797
2798         // always force 3:1 aspect
2799         vector newSize = '0 0 0';
2800         if(mySize.x/mySize.y > 3)
2801         {
2802                 newSize.x = 3 * mySize.y;
2803                 newSize.y = mySize.y;
2804
2805                 pos.x = pos.x + (mySize.x - newSize.x) / 2;
2806         }
2807         else
2808         {
2809                 newSize.y = 1/3 * mySize.x;
2810                 newSize.x = mySize.x;
2811
2812                 pos.y = pos.y + (mySize.y - newSize.y) / 2;
2813         }
2814         mySize = newSize;
2815
2816         s = _("A vote has been called for:");
2817         if(uid2name_dialog)
2818                 s = _("Allow servers to store and display your name?");
2819         drawstring_aspect(pos, s, eX * mySize.x + eY * (2/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2820         s = textShortenToWidth(vote_called_vote, mySize.x, '1 1 0' * mySize.y * (1/8), stringwidth_colors);
2821         if(autocvar__hud_configure)
2822                 s = _("^1Configure the HUD");
2823         drawcolorcodedstring_aspect(pos + eY * (2/8) * mySize.y, s, eX * mySize.x + eY * (1.75/8) * mySize.y, a, DRAWFLAG_NORMAL);
2824
2825         // print the yes/no counts
2826     s = sprintf(_("Yes (%s): %d"), getcommandkey("vyes", "vyes"), vote_yescount);
2827         drawstring_aspect(pos + eY * (4/8) * mySize.y, s, eX * 0.5 * mySize.x + eY * (1.5/8) * mySize.y, '0 1 0', a, DRAWFLAG_NORMAL);
2828     s = sprintf(_("No (%s): %d"), getcommandkey("vno", "vno"), vote_nocount);
2829         drawstring_aspect(pos + eX * 0.5 * mySize.x + eY * (4/8) * mySize.y, s, eX * 0.5 * mySize.x + eY * (1.5/8) * mySize.y, '1 0 0', a, DRAWFLAG_NORMAL);
2830
2831         // draw the progress bar backgrounds
2832         drawpic_skin(pos + eY * (5/8) * mySize.y, "voteprogress_back", eX * mySize.x + eY * (3/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2833
2834         // draw the highlights
2835         if(vote_highlighted == 1) {
2836                 drawsetcliparea(pos.x, pos.y, mySize.x * 0.5, mySize.y);
2837                 drawpic_skin(pos + eY * (5/8) * mySize.y, "voteprogress_voted", eX * mySize.x + eY * (3/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2838         }
2839         else if(vote_highlighted == -1) {
2840                 drawsetcliparea(pos.x + 0.5 * mySize.x, pos.y, mySize.x * 0.5, mySize.y);
2841                 drawpic_skin(pos + eY * (5/8) * mySize.y, "voteprogress_voted", eX * mySize.x + eY * (3/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2842         }
2843
2844         // draw the progress bars
2845         if(vote_yescount && vote_needed)
2846         {
2847                 drawsetcliparea(pos.x, pos.y, mySize.x * 0.5 * (vote_yescount/vote_needed), mySize.y);
2848                 drawpic_skin(pos + eY * (5/8) * mySize.y, "voteprogress_prog", eX * mySize.x + eY * (3/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2849         }
2850
2851         if(vote_nocount && vote_needed)
2852         {
2853                 drawsetcliparea(pos.x + mySize.x - mySize.x * 0.5 * (vote_nocount/vote_needed), pos.y, mySize.x * 0.5, mySize.y);
2854                 drawpic_skin(pos + eY * (5/8) * mySize.y, "voteprogress_prog", eX * mySize.x + eY * (3/8) * mySize.y, '1 1 1', a, DRAWFLAG_NORMAL);
2855         }
2856
2857         drawresetcliparea();
2858 }
2859
2860 // Mod icons panel (#10)
2861 //
2862
2863 bool mod_active; // is there any active mod icon?
2864
2865 void DrawCAItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
2866 {
2867         int stat = -1;
2868         string pic = "";
2869         vector color = '0 0 0';
2870         switch(i)
2871         {
2872                 case 0:
2873                         stat = getstati(STAT_REDALIVE);
2874                         pic = "player_red.tga";
2875                         color = '1 0 0';
2876                         break;
2877                 case 1:
2878                         stat = getstati(STAT_BLUEALIVE);
2879                         pic = "player_blue.tga";
2880                         color = '0 0 1';
2881                         break;
2882                 case 2:
2883                         stat = getstati(STAT_YELLOWALIVE);
2884                         pic = "player_yellow.tga";
2885                         color = '1 1 0';
2886                         break;
2887                 default:
2888                 case 3:
2889                         stat = getstati(STAT_PINKALIVE);
2890                         pic = "player_pink.tga";
2891                         color = '1 0 1';
2892                         break;
2893         }
2894
2895         if(mySize.x/mySize.y > aspect_ratio)
2896         {
2897                 i = aspect_ratio * mySize.y;
2898                 myPos.x = myPos.x + (mySize.x - i) / 2;
2899                 mySize.x = i;
2900         }
2901         else
2902         {
2903                 i = 1/aspect_ratio * mySize.x;
2904                 myPos.y = myPos.y + (mySize.y - i) / 2;
2905                 mySize.y = i;
2906         }
2907
2908         if(layout)
2909         {
2910                 drawpic_aspect_skin(myPos, pic, eX * 0.7 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2911                 drawstring_aspect(myPos + eX * 0.7 * mySize.x, ftos(stat), eX * 0.3 * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
2912         }
2913         else
2914                 drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
2915 }
2916
2917 // Clan Arena and Freeze Tag HUD modicons
2918 void HUD_Mod_CA(vector myPos, vector mySize)
2919 {
2920         mod_active = 1; // required in each mod function that always shows something
2921
2922         int layout;
2923         if(gametype == MAPINFO_TYPE_CA)
2924                 layout = autocvar_hud_panel_modicons_ca_layout;
2925         else //if(gametype == MAPINFO_TYPE_FREEZETAG)
2926                 layout = autocvar_hud_panel_modicons_freezetag_layout;
2927         int rows, columns;
2928         float aspect_ratio;
2929         aspect_ratio = (layout) ? 2 : 1;
2930         rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
2931         columns = ceil(team_count/rows);
2932
2933         int i;
2934         float row = 0, column = 0;
2935         vector pos, itemSize;
2936         itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
2937         for(i=0; i<team_count; ++i)
2938         {
2939                 pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
2940
2941                 DrawCAItem(pos, itemSize, aspect_ratio, layout, i);
2942
2943                 ++row;
2944                 if(row >= rows)
2945                 {
2946                         row = 0;
2947                         ++column;
2948                 }
2949         }
2950 }
2951
2952 // CTF HUD modicon section
2953 int redflag_prevframe, blueflag_prevframe, yellowflag_prevframe, pinkflag_prevframe, neutralflag_prevframe; // status during previous frame
2954 int redflag_prevstatus, blueflag_prevstatus, yellowflag_prevstatus, pinkflag_prevstatus, neutralflag_prevstatus; // last remembered status
2955 float redflag_statuschange_time, blueflag_statuschange_time, yellowflag_statuschange_time, pinkflag_statuschange_time, neutralflag_statuschange_time; // time when the status changed
2956
2957 void HUD_Mod_CTF_Reset(void)
2958 {
2959         redflag_prevstatus = blueflag_prevstatus = yellowflag_prevstatus = pinkflag_prevstatus = neutralflag_prevstatus = 0;
2960         redflag_prevframe = blueflag_prevframe = yellowflag_prevframe = pinkflag_prevframe = neutralflag_prevframe = 0;
2961         redflag_statuschange_time = blueflag_statuschange_time = yellowflag_statuschange_time = pinkflag_statuschange_time = neutralflag_statuschange_time = 0;
2962 }
2963
2964 void HUD_Mod_CTF(vector pos, vector mySize)
2965 {
2966         vector redflag_pos, blueflag_pos, yellowflag_pos, pinkflag_pos, neutralflag_pos;
2967         vector flag_size;
2968         float f; // every function should have that
2969
2970         int redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status
2971         float redflag_statuschange_elapsedtime, blueflag_statuschange_elapsedtime, yellowflag_statuschange_elapsedtime, pinkflag_statuschange_elapsedtime, neutralflag_statuschange_elapsedtime; // time since the status changed
2972         bool ctf_oneflag; // one-flag CTF mode enabled/disabled
2973         int stat_items = getstati(STAT_CTF_FLAGSTATUS, 0, 24);
2974         float fs, fs2, fs3, size1, size2;
2975         vector e1, e2;
2976
2977         redflag = (stat_items/CTF_RED_FLAG_TAKEN) & 3;
2978         blueflag = (stat_items/CTF_BLUE_FLAG_TAKEN) & 3;
2979         yellowflag = (stat_items/CTF_YELLOW_FLAG_TAKEN) & 3;
2980         pinkflag = (stat_items/CTF_PINK_FLAG_TAKEN) & 3;
2981         neutralflag = (stat_items/CTF_NEUTRAL_FLAG_TAKEN) & 3;
2982
2983         ctf_oneflag = (stat_items & CTF_FLAG_NEUTRAL);
2984
2985         mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag);
2986
2987         if (autocvar__hud_configure) {
2988                 redflag = 1;
2989                 blueflag = 2;
2990                 if (team_count >= 3)
2991                         yellowflag = 2;
2992                 if (team_count >= 4)
2993                         pinkflag = 3;
2994                 ctf_oneflag = neutralflag = 0; // disable neutral flag in hud editor?
2995         }
2996
2997         // when status CHANGES, set old status into prevstatus and current status into status
2998         #define X(team) do {                                                                                                                    \
2999                 if (team##flag != team##flag_prevframe) {                                                                       \
3000                 team##flag_statuschange_time = time;                                                                    \
3001                 team##flag_prevstatus = team##flag_prevframe;                                                   \
3002                 team##flag_prevframe = team##flag;                                                                              \
3003         }                                                                                                                                                       \
3004         team##flag_statuschange_elapsedtime = time - team##flag_statuschange_time;      \
3005     } while (0)
3006         X(red);
3007         X(blue);
3008         X(yellow);
3009         X(pink);
3010         X(neutral);
3011         #undef X
3012
3013         const float BLINK_FACTOR = 0.15;
3014         const float BLINK_BASE = 0.85;
3015         // note:
3016         //   RMS = sqrt(BLINK_BASE^2 + 0.5 * BLINK_FACTOR^2)
3017         // thus
3018         //   BLINK_BASE = sqrt(RMS^2 - 0.5 * BLINK_FACTOR^2)
3019         // ensure RMS == 1
3020         const float BLINK_FREQ = 5; // circle frequency, = 2*pi*frequency in hertz
3021
3022         #define X(team, cond) \
3023         string team##_icon, team##_icon_prevstatus; \
3024         int team##_alpha, team##_alpha_prevstatus; \
3025         team##_alpha = team##_alpha_prevstatus = 1; \
3026         do { \
3027                 switch (team##flag) { \
3028                         case 1: team##_icon = "flag_" #team "_taken"; break; \
3029                         case 2: team##_icon = "flag_" #team "_lost"; break; \
3030                         case 3: team##_icon = "flag_" #team "_carrying"; team##_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
3031                         default: \
3032                                 if ((stat_items & CTF_SHIELDED) && (cond)) { \
3033                                         team##_icon = "flag_" #team "_shielded"; \
3034                                 } else { \
3035                                         team##_icon = string_null; \
3036                                 } \
3037                                 break; \
3038                 } \
3039                 switch (team##flag_prevstatus) { \
3040                         case 1: team##_icon_prevstatus = "flag_" #team "_taken"; break; \
3041                         case 2: team##_icon_prevstatus = "flag_" #team "_lost"; break; \
3042                         case 3: team##_icon_prevstatus = "flag_" #team "_carrying"; team##_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
3043                         default: \
3044                                 if (team##flag == 3) { \
3045                                         team##_icon_prevstatus = "flag_" #team "_carrying"; /* make it more visible */\
3046                                 } else if((stat_items & CTF_SHIELDED) && (cond)) { \
3047                                         team##_icon_prevstatus = "flag_" #team "_shielded"; \
3048                                 } else { \
3049                                         team##_icon_prevstatus = string_null; \
3050                                 } \
3051                                 break; \
3052                 } \
3053         } while (0)
3054         X(red, myteam != NUM_TEAM_1);
3055         X(blue, myteam != NUM_TEAM_2);
3056         X(yellow, myteam != NUM_TEAM_3);
3057         X(pink, myteam != NUM_TEAM_4);
3058         X(neutral, true);
3059         #undef X
3060
3061         if (ctf_oneflag) {
3062                 // hacky, but these aren't needed
3063                 red_icon = red_icon_prevstatus = blue_icon = blue_icon_prevstatus = yellow_icon = yellow_icon_prevstatus = pink_icon = pink_icon_prevstatus = string_null;
3064                 fs = fs2 = fs3 = 1;
3065         } else switch (team_count) {
3066                 default:
3067                 case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break;
3068                 case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break;
3069                 case 4: fs = 0.75; fs2 = 0.25; fs3 = 0.5; break;
3070         }
3071
3072         if (mySize_x > mySize_y) {
3073                 size1 = mySize_x;
3074                 size2 = mySize_y;
3075                 e1 = eX;
3076                 e2 = eY;
3077         } else {
3078                 size1 = mySize_y;
3079                 size2 = mySize_x;
3080                 e1 = eY;
3081                 e2 = eX;
3082         }
3083
3084         switch (myteam) {
3085                 default:
3086                 case NUM_TEAM_1: {
3087                         redflag_pos = pos;
3088                         blueflag_pos = pos + eX * fs2 * size1;
3089                         yellowflag_pos = pos - eX * fs2 * size1;
3090                         pinkflag_pos = pos + eX * fs3 * size1;
3091                         break;
3092                 }
3093                 case NUM_TEAM_2: {
3094                         redflag_pos = pos + eX * fs2 * size1;
3095                         blueflag_pos = pos;
3096                         yellowflag_pos = pos - eX * fs2 * size1;
3097                         pinkflag_pos = pos + eX * fs3 * size1;
3098                         break;
3099                 }
3100                 case NUM_TEAM_3: {
3101                         redflag_pos = pos + eX * fs3 * size1;
3102                         blueflag_pos = pos - eX * fs2 * size1;
3103                         yellowflag_pos = pos;
3104                         pinkflag_pos = pos + eX * fs2 * size1;
3105                         break;
3106                 }
3107                 case NUM_TEAM_4: {
3108                         redflag_pos = pos - eX * fs2 * size1;
3109                         blueflag_pos = pos + eX * fs3 * size1;
3110                         yellowflag_pos = pos + eX * fs2 * size1;
3111                         pinkflag_pos = pos;
3112                         break;
3113                 }
3114         }
3115         neutralflag_pos = pos;
3116         flag_size = e1 * fs * size1 + e2 * size2;
3117
3118         #define X(team) do { \
3119                 f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \
3120                 if (team##_icon_prevstatus && f < 1) \
3121                         drawpic_aspect_skin_expanding(team##flag_pos, team##_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * team##_alpha_prevstatus, DRAWFLAG_NORMAL, f); \
3122                 if (team##_icon) \
3123                         drawpic_aspect_skin(team##flag_pos, team##_icon, flag_size, '1 1 1', panel_fg_alpha * team##_alpha * f, DRAWFLAG_NORMAL); \
3124         } while (0)
3125         X(red);
3126         X(blue);
3127         X(yellow);
3128         X(pink);
3129         X(neutral);
3130         #undef X
3131 }
3132
3133 // Keyhunt HUD modicon section
3134 vector KH_SLOTS[4];
3135
3136 void HUD_Mod_KH(vector pos, vector mySize)
3137 {
3138         mod_active = 1; // keyhunt should never hide the mod icons panel
3139
3140         // Read current state
3141
3142         int state = getstati(STAT_KH_KEYS);
3143         int i, key_state;
3144         int all_keys, team1_keys, team2_keys, team3_keys, team4_keys, dropped_keys, carrying_keys;
3145         all_keys = team1_keys = team2_keys = team3_keys = team4_keys = dropped_keys = carrying_keys = 0;
3146
3147         for(i = 0; i < 4; ++i)
3148         {
3149                 key_state = (bitshift(state, i * -5) & 31) - 1;
3150
3151                 if(key_state == -1)
3152                         continue;
3153
3154                 if(key_state == 30)
3155                 {
3156                         ++carrying_keys;
3157                         key_state = myteam;
3158                 }
3159
3160                 switch(key_state)
3161                 {
3162                         case NUM_TEAM_1: ++team1_keys; break;
3163                         case NUM_TEAM_2: ++team2_keys; break;
3164                         case NUM_TEAM_3: ++team3_keys; break;
3165                         case NUM_TEAM_4: ++team4_keys; break;
3166                         case 29: ++dropped_keys; break;
3167                 }
3168
3169                 ++all_keys;
3170         }
3171
3172         // Calculate slot measurements
3173
3174         vector slot_size;
3175
3176         if(all_keys == 4 && mySize.x * 0.5 < mySize.y && mySize.y * 0.5 < mySize.x)
3177         {
3178                 // Quadratic arrangement
3179                 slot_size = eX * mySize.x * 0.5 + eY * mySize.y * 0.5;
3180                 KH_SLOTS[0] = pos;
3181                 KH_SLOTS[1] = pos + eX * slot_size.x;
3182                 KH_SLOTS[2] = pos + eY * slot_size.y;
3183                 KH_SLOTS[3] = pos + eX * slot_size.x + eY * slot_size.y;
3184         }
3185         else
3186         {
3187                 if(mySize.x > mySize.y)
3188                 {
3189                         // Horizontal arrangement
3190                         slot_size = eX * mySize.x / all_keys + eY * mySize.y;
3191                         for(i = 0; i < all_keys; ++i)
3192                                 KH_SLOTS[i] = pos + eX * slot_size.x * i;
3193                 }
3194                 else
3195                 {
3196                         // Vertical arrangement
3197                         slot_size = eX * mySize.x + eY * mySize.y / all_keys;
3198                         for(i = 0; i < all_keys; ++i)
3199                                 KH_SLOTS[i] = pos + eY * slot_size.y * i;
3200                 }
3201         }
3202
3203         // Make icons blink in case of RUN HERE
3204
3205         float blink = 0.6 + sin(2*M_PI*time) / 2.5; // Oscillate between 0.2 and 1
3206         float alpha;
3207         alpha = 1;
3208
3209         if(carrying_keys)
3210                 switch(myteam)
3211                 {
3212                         case NUM_TEAM_1: if(team1_keys == all_keys) alpha = blink; break;
3213                         case NUM_TEAM_2: if(team2_keys == all_keys) alpha = blink; break;
3214                         case NUM_TEAM_3: if(team3_keys == all_keys) alpha = blink; break;
3215                         case NUM_TEAM_4: if(team4_keys == all_keys) alpha = blink; break;
3216                 }
3217
3218         // Draw icons
3219
3220         i = 0;
3221
3222         while(team1_keys--)
3223                 if(myteam == NUM_TEAM_1 && carrying_keys)
3224                 {
3225                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3226                         --carrying_keys;
3227                 }
3228                 else
3229                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3230
3231         while(team2_keys--)
3232                 if(myteam == NUM_TEAM_2 && carrying_keys)
3233                 {
3234                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3235                         --carrying_keys;
3236                 }
3237                 else
3238                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3239
3240         while(team3_keys--)
3241                 if(myteam == NUM_TEAM_3 && carrying_keys)
3242                 {
3243                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3244                         --carrying_keys;
3245                 }
3246                 else
3247                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3248
3249         while(team4_keys--)
3250                 if(myteam == NUM_TEAM_4 && carrying_keys)
3251                 {
3252                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3253                         --carrying_keys;
3254                 }
3255                 else
3256                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3257
3258         while(dropped_keys--)
3259                 drawpic_aspect_skin(KH_SLOTS[i++], "kh_dropped", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
3260 }
3261
3262 // Keepaway HUD mod icon
3263 int kaball_prevstatus; // last remembered status
3264 float kaball_statuschange_time; // time when the status changed
3265
3266 // we don't need to reset for keepaway since it immediately
3267 // autocorrects prevstatus as to if the player has the ball or not
3268
3269 void HUD_Mod_Keepaway(vector pos, vector mySize)
3270 {
3271         mod_active = 1; // keepaway should always show the mod HUD
3272
3273         float BLINK_FACTOR = 0.15;
3274         float BLINK_BASE = 0.85;
3275         float BLINK_FREQ = 5;
3276         float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
3277
3278         int stat_items = getstati(STAT_ITEMS, 0, 24);
3279         int kaball = (stat_items/IT_KEY1) & 1;
3280
3281         if(kaball != kaball_prevstatus)
3282         {
3283                 kaball_statuschange_time = time;
3284                 kaball_prevstatus = kaball;
3285         }
3286
3287         vector kaball_pos, kaball_size;
3288
3289         if(mySize.x > mySize.y) {
3290                 kaball_pos = pos + eX * 0.25 * mySize.x;
3291                 kaball_size = eX * 0.5 * mySize.x + eY * mySize.y;
3292         } else {
3293                 kaball_pos = pos + eY * 0.25 * mySize.y;
3294                 kaball_size = eY * 0.5 * mySize.y + eX * mySize.x;
3295         }
3296
3297         float kaball_statuschange_elapsedtime = time - kaball_statuschange_time;
3298         float f = bound(0, kaball_statuschange_elapsedtime*2, 1);
3299
3300         if(kaball_prevstatus && f < 1)
3301                 drawpic_aspect_skin_expanding(kaball_pos, "keepawayball_carrying", kaball_size, '1 1 1', panel_fg_alpha * kaball_alpha, DRAWFLAG_NORMAL, f);
3302
3303         if(kaball)
3304                 drawpic_aspect_skin(pos, "keepawayball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL);
3305 }
3306
3307
3308 // Nexball HUD mod icon
3309 void HUD_Mod_NexBall(vector pos, vector mySize)
3310 {
3311         float nb_pb_starttime, dt, p;
3312         int stat_items;
3313
3314         stat_items = getstati(STAT_ITEMS, 0, 24);
3315         nb_pb_starttime = getstatf(STAT_NB_METERSTART);
3316
3317         if (stat_items & IT_KEY1)
3318                 mod_active = 1;
3319         else
3320                 mod_active = 0;
3321
3322         //Manage the progress bar if any
3323         if (nb_pb_starttime > 0)
3324         {
3325                 dt = (time - nb_pb_starttime) % nb_pb_period;
3326                 // one period of positive triangle
3327                 p = 2 * dt / nb_pb_period;
3328                 if (p > 1)
3329                         p = 2 - p;
3330
3331                 HUD_Panel_DrawProgressBar(pos, mySize, "progressbar", p, (mySize.x <= mySize.y), 0, autocvar_hud_progressbar_nexball_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
3332         }
3333
3334         if (stat_items & IT_KEY1)
3335                 drawpic_aspect_skin(pos, "nexball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3336 }
3337
3338 // Race/CTS HUD mod icons
3339 float crecordtime_prev; // last remembered crecordtime
3340 float crecordtime_change_time; // time when crecordtime last changed
3341 float srecordtime_prev; // last remembered srecordtime
3342 float srecordtime_change_time; // time when srecordtime last changed
3343
3344 float race_status_time;
3345 int race_status_prev;
3346 string race_status_name_prev;
3347 void HUD_Mod_Race(vector pos, vector mySize)
3348 {
3349         mod_active = 1; // race should never hide the mod icons panel
3350         entity me;
3351         me = playerslots[player_localnum];
3352         float t, score;
3353         float f; // yet another function has this
3354         score = me.(scores[ps_primary]);
3355
3356         if(!(scores_flags[ps_primary] & SFL_TIME) || teamplay) // race/cts record display on HUD
3357                 return; // no records in the actual race
3358
3359         // clientside personal record
3360         string rr;
3361         if(gametype == MAPINFO_TYPE_CTS)
3362                 rr = CTS_RECORD;
3363         else
3364                 rr = RACE_RECORD;
3365         t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
3366
3367         if(score && (score < t || !t)) {
3368                 db_put(ClientProgsDB, strcat(shortmapname, rr, "time"), ftos(score));
3369                 if(autocvar_cl_autodemo_delete_keeprecords)
3370                 {
3371                         f = autocvar_cl_autodemo_delete;
3372                         f &= ~1;
3373                         cvar_set("cl_autodemo_delete", ftos(f)); // don't delete demo with new record!
3374                 }
3375         }
3376
3377         if(t != crecordtime_prev) {
3378                 crecordtime_prev = t;
3379                 crecordtime_change_time = time;
3380         }
3381
3382         vector textPos, medalPos;
3383         float squareSize;
3384         if(mySize.x > mySize.y) {
3385                 // text on left side
3386                 squareSize = min(mySize.y, mySize.x/2);
3387                 textPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eY * 0.5 * (mySize.y - squareSize);
3388                 medalPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eX * 0.5 * mySize.x + eY * 0.5 * (mySize.y - squareSize);
3389         } else {
3390                 // text on top
3391                 squareSize = min(mySize.x, mySize.y/2);
3392                 textPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eX * 0.5 * (mySize.x - squareSize);
3393                 medalPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eY * 0.5 * mySize.y + eX * 0.5 * (mySize.x - squareSize);
3394         }
3395
3396         f = time - crecordtime_change_time;
3397
3398         if (f > 1) {
3399                 drawstring_aspect(textPos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3400                 drawstring_aspect(textPos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3401         } else {
3402                 drawstring_aspect(textPos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3403                 drawstring_aspect(textPos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3404                 drawstring_aspect_expanding(pos, _("Personal best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
3405                 drawstring_aspect_expanding(pos + eY * 0.25 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
3406         }
3407
3408         // server record
3409         t = race_server_record;
3410         if(t != srecordtime_prev) {
3411                 srecordtime_prev = t;
3412                 srecordtime_change_time = time;
3413         }
3414         f = time - srecordtime_change_time;
3415
3416         if (f > 1) {
3417                 drawstring_aspect(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3418                 drawstring_aspect(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3419         } else {
3420                 drawstring_aspect(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3421                 drawstring_aspect(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3422                 drawstring_aspect_expanding(textPos + eY * 0.5 * squareSize, _("Server best"), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
3423                 drawstring_aspect_expanding(textPos + eY * 0.75 * squareSize, TIME_ENCODED_TOSTRING(t), eX * squareSize + eY * 0.25 * squareSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
3424         }
3425
3426         if (race_status != race_status_prev || race_status_name != race_status_name_prev) {
3427                 race_status_time = time + 5;
3428                 race_status_prev = race_status;
3429                 if (race_status_name_prev)
3430                         strunzone(race_status_name_prev);
3431                 race_status_name_prev = strzone(race_status_name);
3432         }
3433
3434         // race "awards"
3435         float a;
3436         a = bound(0, race_status_time - time, 1);
3437
3438         string s;
3439         s = textShortenToWidth(race_status_name, squareSize, '1 1 0' * 0.1 * squareSize, stringwidth_colors);
3440
3441         float rank;
3442         if(race_status > 0)
3443                 rank = race_CheckName(race_status_name);
3444         else
3445                 rank = 0;
3446         string rankname;
3447         rankname = count_ordinal(rank);
3448
3449         vector namepos;
3450         namepos = medalPos + '0 0.8 0' * squareSize;
3451         vector rankpos;
3452         rankpos = medalPos + '0 0.15 0' * squareSize;
3453
3454         if(race_status == 0)
3455                 drawpic_aspect_skin(medalPos, "race_newfail", '1 1 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3456         else if(race_status == 1) {
3457                 drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newtime", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3458                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
3459                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3460         } else if(race_status == 2) {
3461                 if(race_status_name == GetPlayerName(player_localnum) || !race_myrank || race_myrank < rank)
3462                         drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrankgreen", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3463                 else
3464                         drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrankyellow", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3465                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
3466                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3467         } else if(race_status == 3) {
3468                 drawpic_aspect_skin(medalPos + '0.1 0 0' * squareSize, "race_newrecordserver", '1 1 0' * 0.8 * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3469                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
3470                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
3471         }
3472
3473         if (race_status_time - time <= 0) {
3474                 race_status_prev = -1;
3475                 race_status = -1;
3476                 if(race_status_name)
3477                         strunzone(race_status_name);
3478                 race_status_name = string_null;
3479                 if(race_status_name_prev)
3480                         strunzone(race_status_name_prev);
3481                 race_status_name_prev = string_null;
3482         }
3483 }
3484
3485 void DrawDomItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
3486 {
3487         float stat = -1;
3488         string pic = "";
3489         vector color = '0 0 0';
3490         switch(i)
3491         {
3492                 case 0:
3493                         stat = getstatf(STAT_DOM_PPS_RED);
3494                         pic = "dom_icon_red";
3495                         color = '1 0 0';
3496                         break;
3497                 case 1:
3498                         stat = getstatf(STAT_DOM_PPS_BLUE);
3499                         pic = "dom_icon_blue";
3500                         color = '0 0 1';
3501                         break;
3502                 case 2:
3503                         stat = getstatf(STAT_DOM_PPS_YELLOW);
3504                         pic = "dom_icon_yellow";
3505                         color = '1 1 0';
3506                         break;
3507                 default:
3508                 case 3:
3509                         stat = getstatf(STAT_DOM_PPS_PINK);
3510                         pic = "dom_icon_pink";
3511                         color = '1 0 1';
3512                         break;
3513         }
3514         float pps_ratio = stat / getstatf(STAT_DOM_TOTAL_PPS);
3515
3516         if(mySize.x/mySize.y > aspect_ratio)
3517         {
3518                 i = aspect_ratio * mySize.y;
3519                 myPos.x = myPos.x + (mySize.x - i) / 2;
3520                 mySize.x = i;
3521         }
3522         else
3523         {
3524                 i = 1/aspect_ratio * mySize.x;
3525                 myPos.y = myPos.y + (mySize.y - i) / 2;
3526                 mySize.y = i;
3527         }
3528
3529         if (layout) // show text too
3530         {
3531                 //draw the text
3532                 color *= 0.5 + pps_ratio * (1 - 0.5); // half saturated color at min, full saturated at max
3533                 if (layout == 2) // average pps
3534                         drawstring_aspect(myPos + eX * mySize.y, ftos_decimals(stat, 2), eX * (2/3) * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
3535                 else // percentage of average pps
3536                         drawstring_aspect(myPos + eX * mySize.y, strcat( ftos(floor(pps_ratio*100 + 0.5)), "%" ), eX * (2/3) * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
3537         }
3538
3539         //draw the icon
3540         drawpic_aspect_skin(myPos, pic, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3541         if (stat > 0)
3542         {
3543                 drawsetcliparea(myPos.x, myPos.y + mySize.y * (1 - pps_ratio), mySize.y, mySize.y * pps_ratio);
3544                 drawpic_aspect_skin(myPos, strcat(pic, "-highlighted"), '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3545                 drawresetcliparea();
3546         }
3547 }
3548
3549 void HUD_Mod_Dom(vector myPos, vector mySize)
3550 {
3551         mod_active = 1; // required in each mod function that always shows something
3552
3553         int layout = autocvar_hud_panel_modicons_dom_layout;
3554         int rows, columns;
3555         float aspect_ratio;
3556         aspect_ratio = (layout) ? 3 : 1;
3557         rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
3558         columns = ceil(team_count/rows);
3559
3560         int i;
3561         float row = 0, column = 0;
3562         vector pos, itemSize;
3563         itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
3564         for(i=0; i<team_count; ++i)
3565         {
3566                 pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
3567
3568                 DrawDomItem(pos, itemSize, aspect_ratio, layout, i);
3569
3570                 ++row;
3571                 if(row >= rows)
3572                 {
3573                         row = 0;
3574                         ++column;
3575                 }
3576         }
3577 }
3578
3579 void HUD_ModIcons_SetFunc()
3580 {
3581         switch(gametype)
3582         {
3583                 case MAPINFO_TYPE_KEYHUNT:              HUD_ModIcons_GameType = HUD_Mod_KH; break;
3584                 case MAPINFO_TYPE_CTF:                  HUD_ModIcons_GameType = HUD_Mod_CTF; break;
3585                 case MAPINFO_TYPE_NEXBALL:              HUD_ModIcons_GameType = HUD_Mod_NexBall; break;
3586                 case MAPINFO_TYPE_CTS:
3587                 case MAPINFO_TYPE_RACE:         HUD_ModIcons_GameType = HUD_Mod_Race; break;
3588                 case MAPINFO_TYPE_CA:
3589                 case MAPINFO_TYPE_FREEZETAG:    HUD_ModIcons_GameType = HUD_Mod_CA; break;
3590                 case MAPINFO_TYPE_DOMINATION:   HUD_ModIcons_GameType = HUD_Mod_Dom; break;
3591                 case MAPINFO_TYPE_KEEPAWAY:     HUD_ModIcons_GameType = HUD_Mod_Keepaway; break;
3592         }
3593 }
3594
3595 int mod_prev; // previous state of mod_active to check for a change
3596 float mod_alpha;
3597 float mod_change; // "time" when mod_active changed
3598
3599 void HUD_ModIcons(void)
3600 {
3601         if(!autocvar__hud_configure)
3602         {
3603                 if(!autocvar_hud_panel_modicons) return;
3604                 if(!HUD_ModIcons_GameType) return;
3605         }
3606
3607         HUD_Panel_UpdateCvars();
3608
3609         draw_beginBoldFont();
3610
3611         if(mod_active != mod_prev) {
3612                 mod_change = time;
3613                 mod_prev = mod_active;
3614         }
3615
3616         if(mod_active || autocvar__hud_configure)
3617                 mod_alpha = bound(0, (time - mod_change) * 2, 1);
3618         else
3619                 mod_alpha = bound(0, 1 - (time - mod_change) * 2, 1);
3620
3621         if(mod_alpha)
3622                 HUD_Panel_DrawBg(mod_alpha);
3623
3624         if(panel_bg_padding)
3625         {
3626                 panel_pos += '1 1 0' * panel_bg_padding;
3627                 panel_size -= '2 2 0' * panel_bg_padding;
3628         }
3629
3630         if(autocvar__hud_configure)
3631                 HUD_Mod_CTF(panel_pos, panel_size);
3632         else
3633                 HUD_ModIcons_GameType(panel_pos, panel_size);
3634
3635         draw_endBoldFont();
3636 }
3637
3638 // Draw pressed keys (#11)
3639 //
3640 void HUD_PressedKeys(void)
3641 {
3642         if(!autocvar__hud_configure)
3643         {
3644                 if(!autocvar_hud_panel_pressedkeys) return;
3645                 if(spectatee_status <= 0 && autocvar_hud_panel_pressedkeys < 2) return;
3646         }
3647
3648         HUD_Panel_UpdateCvars();
3649         vector pos, mySize;
3650         pos = panel_pos;
3651         mySize = panel_size;
3652
3653         HUD_Panel_DrawBg(1);
3654         if(panel_bg_padding)
3655         {
3656                 pos += '1 1 0' * panel_bg_padding;
3657                 mySize -= '2 2 0' * panel_bg_padding;
3658         }
3659
3660         // force custom aspect
3661         float aspect = autocvar_hud_panel_pressedkeys_aspect;
3662         if(aspect)
3663         {
3664                 vector newSize = '0 0 0';
3665                 if(mySize.x/mySize.y > aspect)
3666                 {
3667                         newSize.x = aspect * mySize.y;
3668                         newSize.y = mySize.y;
3669
3670                         pos.x = pos.x + (mySize.x - newSize.x) / 2;
3671                 }
3672                 else
3673                 {
3674                         newSize.y = 1/aspect * mySize.x;
3675                         newSize.x = mySize.x;
3676
3677                         pos.y = pos.y + (mySize.y - newSize.y) / 2;
3678                 }
3679                 mySize = newSize;
3680         }
3681
3682         vector keysize;
3683         keysize = eX * mySize.x * (1/3.0) + eY * mySize.y * (1/(3.0 - !autocvar_hud_panel_pressedkeys_attack));
3684         float pressedkeys;
3685         pressedkeys = getstatf(STAT_PRESSED_KEYS);
3686
3687         if(autocvar_hud_panel_pressedkeys_attack)
3688         {
3689                 drawpic_aspect_skin(pos + eX * keysize.x * 0.5, ((pressedkeys & KEY_ATCK) ? "key_atck_inv.tga" : "key_atck.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3690                 drawpic_aspect_skin(pos + eX * keysize.x * 1.5, ((pressedkeys & KEY_ATCK2) ? "key_atck_inv.tga" : "key_atck.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3691                 pos.y += keysize.y;
3692         }
3693
3694         drawpic_aspect_skin(pos, ((pressedkeys & KEY_CROUCH) ? "key_crouch_inv.tga" : "key_crouch.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3695         drawpic_aspect_skin(pos + eX * keysize.x, ((pressedkeys & KEY_FORWARD) ? "key_forward_inv.tga" : "key_forward.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3696         drawpic_aspect_skin(pos + eX * keysize.x * 2, ((pressedkeys & KEY_JUMP) ? "key_jump_inv.tga" : "key_jump.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3697         pos.y += keysize.y;
3698         drawpic_aspect_skin(pos, ((pressedkeys & KEY_LEFT) ? "key_left_inv.tga" : "key_left.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3699         drawpic_aspect_skin(pos + eX * keysize.x, ((pressedkeys & KEY_BACKWARD) ? "key_backward_inv.tga" : "key_backward.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3700         drawpic_aspect_skin(pos + eX * keysize.x * 2, ((pressedkeys & KEY_RIGHT) ? "key_right_inv.tga" : "key_right.tga"), keysize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3701 }
3702
3703 // Handle chat as a panel (#12)
3704 //
3705 void HUD_Chat(void)
3706 {
3707         if(!autocvar__hud_configure)
3708         {
3709                 if (!autocvar_hud_panel_chat)
3710                 {
3711                         if (!autocvar_con_chatrect)
3712                                 cvar_set("con_chatrect", "0");
3713                         return;
3714                 }
3715                 if(autocvar__con_chat_maximized)
3716                 {
3717                         if(!hud_draw_maximized) return;
3718                 }
3719                 else if(chat_panel_modified)
3720                 {
3721                         panel.update_time = time; // forces reload of panel attributes
3722                         chat_panel_modified = false;
3723                 }
3724         }
3725
3726         HUD_Panel_UpdateCvars();
3727
3728         if(intermission == 2)
3729         {
3730                 // reserve some more space to the mapvote panel
3731                 // by resizing and moving chat panel to the bottom
3732                 panel_size.y = min(panel_size.y, vid_conheight * 0.2);
3733                 panel_pos.y = vid_conheight - panel_size.y - panel_bg_border * 2;
3734                 chat_posy = panel_pos.y;
3735                 chat_sizey = panel_size.y;
3736         }
3737         if(autocvar__con_chat_maximized && !autocvar__hud_configure) // draw at full screen height if maximized
3738         {
3739                 panel_pos.y = panel_bg_border;
3740                 panel_size.y = vid_conheight - panel_bg_border * 2;
3741                 if(panel.current_panel_bg == "0") // force a border when maximized
3742                 {
3743                         string panel_bg;
3744                         panel_bg = strcat(hud_skin_path, "/border_default");
3745                         if(precache_pic(panel_bg) == "")
3746                                 panel_bg = "gfx/hud/default/border_default";
3747                         if(panel.current_panel_bg)
3748                                 strunzone(panel.current_panel_bg);
3749                         panel.current_panel_bg = strzone(panel_bg);
3750                         chat_panel_modified = true;
3751                 }
3752                 panel_bg_alpha = max(0.75, panel_bg_alpha); // force an theAlpha of at least 0.75
3753         }
3754
3755         vector pos, mySize;
3756         pos = panel_pos;
3757         mySize = panel_size;
3758
3759         HUD_Panel_DrawBg(1);
3760
3761         if(panel_bg_padding)
3762         {
3763                 pos += '1 1 0' * panel_bg_padding;
3764                 mySize -= '2 2 0' * panel_bg_padding;
3765         }
3766
3767         if (!autocvar_con_chatrect)
3768                 cvar_set("con_chatrect", "1");
3769
3770         cvar_set("con_chatrect_x", ftos(pos.x/vid_conwidth));
3771         cvar_set("con_chatrect_y", ftos(pos.y/vid_conheight));
3772
3773         cvar_set("con_chatwidth", ftos(mySize.x/vid_conwidth));
3774         cvar_set("con_chat", ftos(floor(mySize.y/autocvar_con_chatsize - 0.5)));
3775
3776         if(autocvar__hud_configure)
3777         {
3778                 vector chatsize;
3779                 chatsize = '1 1 0' * autocvar_con_chatsize;
3780                 cvar_set("con_chatrect_x", "9001"); // over 9000, we'll fake it instead for more control over theAlpha and such
3781                 float i, a;
3782                 for(i = 0; i < autocvar_con_chat; ++i)
3783                 {
3784                         if(i == autocvar_con_chat - 1)
3785                                 a = panel_fg_alpha;
3786                         else
3787                                 a = panel_fg_alpha * floor(((i + 1) * 7 + autocvar_con_chattime)/45);
3788                         drawcolorcodedstring(pos, textShortenToWidth(_("^3Player^7: This is the chat area."), mySize.x, chatsize, stringwidth_colors), chatsize, a, DRAWFLAG_NORMAL);
3789                         pos.y += chatsize.y;
3790                 }
3791         }
3792 }
3793
3794 // Engine info panel (#13)
3795 //
3796 float prevfps;
3797 float prevfps_time;
3798 int framecounter;
3799
3800 float frametimeavg;
3801 float frametimeavg1; // 1 frame ago
3802 float frametimeavg2; // 2 frames ago
3803 void HUD_EngineInfo(void)
3804 {
3805         if(!autocvar__hud_configure)
3806         {
3807                 if(!autocvar_hud_panel_engineinfo) return;
3808         }
3809
3810         HUD_Panel_UpdateCvars();
3811         vector pos, mySize;
3812         pos = panel_pos;
3813         mySize = panel_size;
3814
3815         HUD_Panel_DrawBg(1);
3816         if(panel_bg_padding)
3817         {
3818                 pos += '1 1 0' * panel_bg_padding;
3819                 mySize -= '2 2 0' * panel_bg_padding;
3820         }
3821
3822         float currentTime = gettime(GETTIME_REALTIME);
3823         if(cvar("hud_panel_engineinfo_framecounter_exponentialmovingaverage"))
3824         {
3825                 float currentframetime = currentTime - prevfps_time;
3826                 frametimeavg = (frametimeavg + frametimeavg1 + frametimeavg2 + currentframetime)/4; // average three frametimes into framecounter for slightly more stable fps readings :P
3827                 frametimeavg2 = frametimeavg1;
3828                 frametimeavg1 = frametimeavg;
3829
3830                 float weight;
3831                 weight = cvar("hud_panel_engineinfo_framecounter_exponentialmovingaverage_new_weight");
3832                 if(currentframetime > 0.0001) // filter out insane values which sometimes seem to occur and throw off the average? If you are getting 10,000 fps or more, then you don't need a framerate counter.
3833                 {
3834                         if(fabs(prevfps - (1/frametimeavg)) > prevfps * cvar("hud_panel_engineinfo_framecounter_exponentialmovingaverage_instantupdate_change_threshold")) // if there was a big jump in fps, just force prevfps at current (1/currentframetime) to make big updates instant
3835                                 prevfps = (1/currentframetime);
3836                         prevfps = (1 - weight) * prevfps + weight * (1/frametimeavg); // framecounter just used so there's no need for a new variable, think of it as "frametime average"
3837                 }
3838                 prevfps_time = currentTime;
3839         }
3840         else
3841         {
3842                 framecounter += 1;
3843                 if(currentTime - prevfps_time > autocvar_hud_panel_engineinfo_framecounter_time)
3844                 {
3845                         prevfps = framecounter/(currentTime - prevfps_time);
3846                         framecounter = 0;
3847                         prevfps_time = currentTime;
3848                 }
3849         }
3850
3851         vector color;
3852         color = HUD_Get_Num_Color (prevfps, 100);
3853         drawstring_aspect(pos, sprintf(_("FPS: %.*f"), autocvar_hud_panel_engineinfo_framecounter_decimals, prevfps), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
3854 }
3855
3856 // Info messages panel (#14)
3857 //
3858 #define drawInfoMessage(s) do {                                                                                                                                                                         \
3859         if(autocvar_hud_panel_infomessages_flip)                                                                                                                                                \
3860                 o.x = pos.x + mySize.x - stringwidth(s, true, fontsize);                                                                                                        \
3861         drawcolorcodedstring(o, s, fontsize, a, DRAWFLAG_NORMAL);                                                                                                               \
3862         o.y += fontsize.y;                                                                                                                                                                                              \
3863 } while(0)
3864 void HUD_InfoMessages(void)
3865 {
3866         if(!autocvar__hud_configure)
3867         {
3868                 if(!autocvar_hud_panel_infomessages) return;
3869         }
3870
3871         HUD_Panel_UpdateCvars();
3872         vector pos, mySize;
3873         pos = panel_pos;
3874         mySize = panel_size;
3875
3876         HUD_Panel_DrawBg(1);
3877         if(panel_bg_padding)
3878         {
3879                 pos += '1 1 0' * panel_bg_padding;
3880                 mySize -= '2 2 0' * panel_bg_padding;
3881         }
3882
3883         // always force 5:1 aspect
3884         vector newSize = '0 0 0';
3885         if(mySize.x/mySize.y > 5)
3886         {
3887                 newSize.x = 5 * mySize.y;
3888                 newSize.y = mySize.y;
3889
3890                 pos.x = pos.x + (mySize.x - newSize.x) / 2;
3891         }
3892         else
3893         {
3894                 newSize.y = 1/5 * mySize.x;
3895                 newSize.x = mySize.x;
3896
3897                 pos.y = pos.y + (mySize.y - newSize.y) / 2;
3898         }
3899
3900         mySize = newSize;
3901         entity tm;
3902         vector o;
3903         o = pos;
3904
3905         vector fontsize;
3906         fontsize = '0.20 0.20 0' * mySize.y;
3907
3908         float a;
3909         a = panel_fg_alpha;
3910
3911         string s;
3912         if(!autocvar__hud_configure)
3913         {
3914                 if(spectatee_status && !intermission)
3915                 {
3916                         a = 1;
3917                         if(spectatee_status == -1)
3918                                 s = _("^1Observing");
3919                         else
3920                                 s = sprintf(_("^1Spectating: ^7%s"), GetPlayerName(current_player));
3921                         drawInfoMessage(s);
3922
3923                         if(spectatee_status == -1)
3924                                 s = sprintf(_("^1Press ^3%s^1 to spectate"), getcommandkey("primary fire", "+fire"));
3925                         else
3926                                 s = sprintf(_("^1Press ^3%s^1 or ^3%s^1 for next or previous player"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev"));
3927                         drawInfoMessage(s);
3928
3929                         if(spectatee_status == -1)
3930                                 s = sprintf(_("^1Use ^3%s^1 or ^3%s^1 to change the speed"), getcommandkey("next weapon", "weapnext"), getcommandkey("previous weapon", "weapprev"));
3931                         else
3932                                 s = sprintf(_("^1Press ^3%s^1 to observe"), getcommandkey("secondary fire", "+fire2"));
3933                         drawInfoMessage(s);
3934
3935                         s = sprintf(_("^1Press ^3%s^1 for gamemode info"), getcommandkey("server info", "+show_info"));
3936                         drawInfoMessage(s);
3937
3938                         if(gametype == MAPINFO_TYPE_LMS)
3939                         {
3940                                 entity sk;
3941                                 sk = playerslots[player_localnum];
3942                                 if(sk.(scores[ps_primary]) >= 666)
3943                                         s = _("^1Match has already begun");
3944                                 else if(sk.(scores[ps_primary]) > 0)
3945                                         s = _("^1You have no more lives left");
3946                                 else
3947                                         s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump"));
3948                         }
3949                         else
3950                                 s = sprintf(_("^1Press ^3%s^1 to join"), getcommandkey("jump", "+jump"));
3951                         drawInfoMessage(s);
3952
3953                         //show restart countdown:
3954                         if (time < getstatf(STAT_GAMESTARTTIME)) {
3955                                 float countdown;
3956                                 //we need to ceil, otherwise the countdown would be off by .5 when using round()
3957                                 countdown = ceil(getstatf(STAT_GAMESTARTTIME) - time);
3958                                 s = sprintf(_("^1Game starts in ^3%d^1 seconds"), countdown);
3959                                 drawcolorcodedstring(o, s, fontsize, a, DRAWFLAG_NORMAL);
3960                                 o.y += fontsize.y;
3961                         }
3962                 }
3963                 if(warmup_stage && !intermission)
3964                 {
3965                         s = _("^2Currently in ^1warmup^2 stage!");
3966                         drawInfoMessage(s);
3967                 }
3968
3969                 string blinkcolor;
3970                 if(time % 1 >= 0.5)
3971                         blinkcolor = "^1";
3972                 else
3973                         blinkcolor = "^3";
3974
3975                 if(ready_waiting && !intermission && !spectatee_status)
3976                 {
3977                         if(ready_waiting_for_me)
3978                         {
3979                                 if(warmup_stage)
3980                                         s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor);
3981                                 else
3982                                         s = sprintf(_("%sPress ^3%s%s once you are ready"), blinkcolor, getcommandkey("ready", "ready"), blinkcolor);
3983                         }
3984                         else
3985                         {
3986                                 if(warmup_stage)
3987                                         s = _("^2Waiting for others to ready up to end warmup...");
3988                                 else
3989                                         s = _("^2Waiting for others to ready up...");
3990                         }
3991                         drawInfoMessage(s);
3992                 }
3993                 else if(warmup_stage && !intermission && !spectatee_status)
3994                 {
3995                         s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey("ready", "ready"));
3996                         drawInfoMessage(s);
3997                 }
3998
3999                 if(teamplay && !intermission && !spectatee_status && gametype != MAPINFO_TYPE_CA && teamnagger)
4000                 {
4001                         float ts_min = 0, ts_max = 0;
4002                         tm = teams.sort_next;
4003                         if (tm)
4004                         {
4005                                 for (; tm.sort_next; tm = tm.sort_next)
4006                                 {
4007                                         if(!tm.team_size || tm.team == NUM_SPECTATOR)
4008                                                 continue;
4009                                         if(!ts_min) ts_min = tm.team_size;
4010                                         else ts_min = min(ts_min, tm.team_size);
4011                                         if(!ts_max) ts_max = tm.team_size;
4012                                         else ts_max = max(ts_max, tm.team_size);
4013                                 }
4014                                 if ((ts_max - ts_min) > 1)
4015                                 {
4016                                         s = strcat(blinkcolor, _("Teamnumbers are unbalanced!"));
4017                                         tm = GetTeam(myteam, false);
4018                                         if (tm)
4019                                         if (tm.team != NUM_SPECTATOR)
4020                                         if (tm.team_size == ts_max)
4021                                                 s = strcat(s, sprintf(_(" Press ^3%s%s to adjust"), getcommandkey("team menu", "menu_showteamselect"), blinkcolor));
4022                                         drawInfoMessage(s);
4023                                 }
4024                         }
4025                 }
4026         }
4027         else
4028         {
4029                 s = _("^7Press ^3ESC ^7to show HUD options.");
4030                 drawInfoMessage(s);
4031                 s = _("^3Doubleclick ^7a panel for panel-specific options.");
4032                 drawInfoMessage(s);
4033                 s = _("^3CTRL ^7to disable collision testing, ^3SHIFT ^7and");
4034                 drawInfoMessage(s);
4035                 s = _("^3ALT ^7+ ^3ARROW KEYS ^7for fine adjustments.");
4036                 drawInfoMessage(s);
4037         }
4038 }
4039
4040 // Physics panel (#15)
4041 //
4042 vector acc_prevspeed;
4043 float acc_prevtime, acc_avg, top_speed, top_speed_time;
4044 float physics_update_time, discrete_speed, discrete_acceleration;
4045 void HUD_Physics(void)
4046 {
4047         if(!autocvar__hud_configure)
4048         {
4049                 if(!autocvar_hud_panel_physics) return;
4050                 if(spectatee_status == -1 && (autocvar_hud_panel_physics == 1 || autocvar_hud_panel_physics == 3)) return;
4051                 if(autocvar_hud_panel_physics == 3 && !(gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_CTS)) return;
4052         }
4053
4054         HUD_Panel_UpdateCvars();
4055
4056         draw_beginBoldFont();
4057
4058         HUD_Panel_DrawBg(1);
4059         if(panel_bg_padding)
4060         {
4061                 panel_pos += '1 1 0' * panel_bg_padding;
4062                 panel_size -= '2 2 0' * panel_bg_padding;
4063         }
4064
4065         float acceleration_progressbar_scale = 0;
4066         if(autocvar_hud_panel_physics_progressbar && autocvar_hud_panel_physics_acceleration_progressbar_scale > 1)
4067                 acceleration_progressbar_scale = autocvar_hud_panel_physics_acceleration_progressbar_scale;
4068
4069         float text_scale;
4070         if (autocvar_hud_panel_physics_text_scale <= 0)
4071                 text_scale = 1;
4072         else
4073                 text_scale = min(autocvar_hud_panel_physics_text_scale, 1);
4074
4075         //compute speed
4076         float speed, conversion_factor;
4077         string unit;
4078
4079         switch(autocvar_hud_panel_physics_speed_unit)
4080         {
4081                 default:
4082                 case 1:
4083                         unit = _(" qu/s");
4084                         conversion_factor = 1.0;
4085                         break;
4086                 case 2:
4087                         unit = _(" m/s");
4088                         conversion_factor = 0.0254;
4089                         break;
4090                 case 3:
4091                         unit = _(" km/h");
4092                         conversion_factor = 0.0254 * 3.6;
4093                         break;
4094                 case 4:
4095                         unit = _(" mph");
4096                         conversion_factor = 0.0254 * 3.6 * 0.6213711922;
4097                         break;
4098                 case 5:
4099                         unit = _(" knots");
4100                         conversion_factor = 0.0254 * 1.943844492; // 1 m/s = 1.943844492 knots, because 1 knot = 1.852 km/h
4101                         break;
4102         }
4103
4104         vector vel = (csqcplayer ? csqcplayer.velocity : pmove_vel);
4105
4106         float max_speed = floor( autocvar_hud_panel_physics_speed_max * conversion_factor + 0.5 );
4107         if (autocvar__hud_configure)
4108                 speed = floor( max_speed * 0.65 + 0.5 );
4109         else if(autocvar_hud_panel_physics_speed_vertical)
4110                 speed = floor( vlen(vel) * conversion_factor + 0.5 );
4111         else
4112                 speed = floor( vlen(vel - vel.z * '0 0 1') * conversion_factor + 0.5 );
4113
4114         //compute acceleration
4115         float acceleration, f;
4116         if (autocvar__hud_configure)
4117                 acceleration = autocvar_hud_panel_physics_acceleration_max * 0.3;
4118         else
4119         {
4120                 // 1 m/s = 0.0254 qu/s; 1 g = 9.80665 m/s^2
4121                 f = time - acc_prevtime;
4122                 if(autocvar_hud_panel_physics_acceleration_vertical)
4123                         acceleration = (vlen(vel) - vlen(acc_prevspeed));
4124                 else
4125                         acceleration = (vlen(vel - '0 0 1' * vel.z) - vlen(acc_prevspeed - '0 0 1' * acc_prevspeed.z));
4126
4127                 acceleration = acceleration * (1 / max(0.0001, f)) * (0.0254 / 9.80665);
4128
4129                 acc_prevspeed = vel;
4130                 acc_prevtime = time;
4131
4132                 if(autocvar_hud_panel_physics_acceleration_movingaverage)
4133                 {
4134                         f = bound(0, f * 10, 1);
4135                         acc_avg = acc_avg * (1 - f) + acceleration * f;
4136                         acceleration = acc_avg;
4137                 }
4138         }
4139
4140         int acc_decimals = 2;
4141         if(time > physics_update_time)
4142         {
4143                 // workaround for ftos_decimals returning a negative 0
4144                 if(discrete_acceleration > -1 / pow(10, acc_decimals) && discrete_acceleration < 0)
4145                         discrete_acceleration = 0;
4146                 discrete_acceleration = acceleration;
4147                 discrete_speed = speed;
4148                 physics_update_time += autocvar_hud_panel_physics_update_interval;
4149         }
4150
4151         //compute layout
4152         float panel_ar = panel_size.x/panel_size.y;
4153         vector speed_offset = '0 0 0', acceleration_offset = '0 0 0';
4154         if (panel_ar >= 5 && !acceleration_progressbar_scale)
4155         {
4156                 panel_size.x *= 0.5;
4157                 if (autocvar_hud_panel_physics_flip)
4158                         speed_offset.x = panel_size.x;
4159                 else
4160                         acceleration_offset.x = panel_size.x;
4161         }
4162         else
4163         {
4164                 panel_size.y *= 0.5;
4165                 if (autocvar_hud_panel_physics_flip)
4166                         speed_offset.y = panel_size.y;
4167                 else
4168                         acceleration_offset.y = panel_size.y;
4169         }
4170         int speed_baralign, acceleration_baralign;
4171         if (autocvar_hud_panel_physics_baralign == 1)
4172                 acceleration_baralign = speed_baralign = 1;
4173     else if(autocvar_hud_panel_physics_baralign == 4)
4174                 acceleration_baralign = speed_baralign = 2;
4175         else if (autocvar_hud_panel_physics_flip)
4176         {
4177                 acceleration_baralign = (autocvar_hud_panel_physics_baralign == 2);
4178                 speed_baralign = (autocvar_hud_panel_physics_baralign == 3);
4179         }
4180         else
4181         {
4182                 speed_baralign = (autocvar_hud_panel_physics_baralign == 2);
4183                 acceleration_baralign = (autocvar_hud_panel_physics_baralign == 3);
4184         }
4185         if (autocvar_hud_panel_physics_acceleration_progressbar_mode == 0)
4186                 acceleration_baralign = 3; //override hud_panel_physics_baralign value for acceleration
4187
4188         //draw speed
4189         if(speed)
4190         if(autocvar_hud_panel_physics_progressbar == 1 || autocvar_hud_panel_physics_progressbar == 2)
4191                 HUD_Panel_DrawProgressBar(panel_pos + speed_offset, panel_size, "progressbar", speed/max_speed, 0, speed_baralign, autocvar_hud_progressbar_speed_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
4192         vector tmp_offset = '0 0 0', tmp_size = '0 0 0';
4193         if (autocvar_hud_panel_physics_text == 1 || autocvar_hud_panel_physics_text == 2)
4194         {
4195                 tmp_size.x = panel_size.x * 0.75;
4196                 tmp_size.y = panel_size.y * text_scale;
4197                 if (speed_baralign)
4198                         tmp_offset.x = panel_size.x - tmp_size.x;
4199                 //else
4200                         //tmp_offset_x = 0;
4201                 tmp_offset.y = (panel_size.y - tmp_size.y) / 2;
4202                 drawstring_aspect(panel_pos + speed_offset + tmp_offset, ftos(discrete_speed), tmp_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
4203
4204                 //draw speed unit
4205                 if (speed_baralign)
4206                         tmp_offset.x = 0;
4207                 else
4208                         tmp_offset.x = tmp_size.x;
4209                 if (autocvar_hud_panel_physics_speed_unit_show)
4210                 {
4211                         //tmp_offset_y = 0;
4212                         tmp_size.x = panel_size.x * (1 - 0.75);
4213                         tmp_size.y = panel_size.y * 0.4 * text_scale;
4214                         tmp_offset.y = (panel_size.y * 0.4 - tmp_size.y) / 2;
4215                         drawstring_aspect(panel_pos + speed_offset + tmp_offset, unit, tmp_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
4216                 }
4217         }
4218
4219         //compute and draw top speed
4220         if (autocvar_hud_panel_physics_topspeed)
4221         if (autocvar_hud_panel_physics_text == 1 || autocvar_hud_panel_physics_text == 2)
4222         {
4223                 if (autocvar__hud_configure)
4224                 {
4225                         top_speed = floor( max_speed * 0.75 + 0.5 );
4226                         f = 1;
4227                 }
4228                 else
4229                 {
4230                         if (speed >= top_speed)
4231                         {
4232                                 top_speed = speed;
4233                                 top_speed_time = time;
4234                         }
4235                         if (top_speed != 0)
4236                         {
4237                                 f = max(1, autocvar_hud_panel_physics_topspeed_time);
4238                                 // divide by f to make it start from 1
4239                                 f = cos( ((time - top_speed_time) / f) * PI/2 );
4240                         }
4241             else //hide top speed 0, it would be stupid
4242                                 f = 0;
4243                 }
4244                 if (f > 0)
4245                 {
4246                         //top speed progressbar peak
4247                         if(speed < top_speed)
4248                         if(autocvar_hud_panel_physics_progressbar == 1 || autocvar_hud_panel_physics_progressbar == 2)
4249                         {
4250                                 float peak_offsetX;
4251                                 vector peak_size = '0 0 0';
4252                                 if (speed_baralign == 0)
4253                                         peak_offsetX = min(top_speed, max_speed)/max_speed * panel_size.x;
4254                 else if (speed_baralign == 1)
4255                                         peak_offsetX = (1 - min(top_speed, max_speed)/max_speed) * panel_size.x;
4256                 else // if (speed_baralign == 2)
4257                     peak_offsetX = min(top_speed, max_speed)/max_speed * panel_size.x * 0.5;
4258                                 peak_size.x = floor(panel_size.x * 0.01 + 1.5);
4259                 peak_size.y = panel_size.y;
4260                 if (speed_baralign == 2) // draw two peaks, on both sides
4261                 {
4262                     drawfill(panel_pos + speed_offset + eX * (0.5 * panel_size.x + peak_offsetX - peak_size.x), peak_size, autocvar_hud_progressbar_speed_color, f * autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
4263                     drawfill(panel_pos + speed_offset + eX * (0.5 * panel_size.x - peak_offsetX + peak_size.x), peak_size, autocvar_hud_progressbar_speed_color, f * autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
4264                 }
4265                 else
4266                     drawfill(panel_pos + speed_offset + eX * (peak_offsetX - peak_size.x), peak_size, autocvar_hud_progressbar_speed_color, f * autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
4267                         }
4268
4269                         //top speed
4270                         tmp_offset.y = panel_size.y * 0.4;
4271                         tmp_size.x = panel_size.x * (1 - 0.75);
4272                         tmp_size.y = (panel_size.y - tmp_offset.y) * text_scale;
4273                         tmp_offset.y += (panel_size.y - tmp_offset.y - tmp_size.y) / 2;
4274                         drawstring_aspect(panel_pos + speed_offset + tmp_offset, ftos(top_speed), tmp_size, '1 0 0', f * panel_fg_alpha, DRAWFLAG_NORMAL);
4275                 }
4276                 else
4277                         top_speed = 0;
4278         }
4279
4280         //draw acceleration
4281         if(acceleration)
4282         if(autocvar_hud_panel_physics_progressbar == 1 || autocvar_hud_panel_physics_progressbar == 3)
4283         {
4284                 vector progressbar_color;
4285                 if(acceleration < 0)
4286                         progressbar_color = autocvar_hud_progressbar_acceleration_neg_color;
4287                 else
4288                         progressbar_color = autocvar_hud_progressbar_acceleration_color;
4289
4290                 f = acceleration/autocvar_hud_panel_physics_acceleration_max;
4291                 if (autocvar_hud_panel_physics_acceleration_progressbar_nonlinear)
4292                         f = (f >= 0 ? sqrt(f) : -sqrt(-f));
4293
4294                 if (acceleration_progressbar_scale) // allow progressbar to go out of panel bounds
4295                 {
4296                         tmp_size = acceleration_progressbar_scale * panel_size.x * eX + panel_size.y * eY;
4297
4298                         if (acceleration_baralign == 1)
4299                                 tmp_offset.x = panel_size.x - tmp_size.x;
4300                         else if (acceleration_baralign == 2 || acceleration_baralign == 3)
4301                                 tmp_offset.x = (panel_size.x - tmp_size.x) / 2;
4302                         else
4303                                 tmp_offset.x = 0;
4304                         tmp_offset.y = 0;
4305                 }
4306                 else
4307                 {
4308                         tmp_size = panel_size;
4309                         tmp_offset = '0 0 0';
4310                 }
4311
4312                 HUD_Panel_DrawProgressBar(panel_pos + acceleration_offset + tmp_offset, tmp_size, "accelbar", f, 0, acceleration_baralign, progressbar_color, autocvar_hud_progressbar_alpha * panel_fg_alpha, DRAWFLAG_NORMAL);
4313         }
4314
4315         if(autocvar_hud_panel_physics_text == 1 || autocvar_hud_panel_physics_text == 3)
4316         {
4317                 tmp_size.x = panel_size.x;
4318                 tmp_size.y = panel_size.y * text_scale;
4319                 tmp_offset.x = 0;
4320                 tmp_offset.y = (panel_size.y - tmp_size.y) / 2;
4321
4322                 drawstring_aspect(panel_pos + acceleration_offset + tmp_offset, strcat(ftos_decimals(discrete_acceleration, acc_decimals), "g"), tmp_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
4323         }
4324
4325         draw_endBoldFont();
4326 }
4327
4328 // CenterPrint (#16)
4329 //
4330 const int CENTERPRINT_MAX_MSGS = 10;
4331 const int CENTERPRINT_MAX_ENTRIES = 50;
4332 const float CENTERPRINT_SPACING = 0.7;
4333 int cpm_index;
4334 string centerprint_messages[CENTERPRINT_MAX_MSGS];
4335 int centerprint_msgID[CENTERPRINT_MAX_MSGS];
4336 float centerprint_time[CENTERPRINT_MAX_MSGS];
4337 float centerprint_expire_time[CENTERPRINT_MAX_MSGS];
4338 int centerprint_countdown_num[CENTERPRINT_MAX_MSGS];
4339 bool centerprint_showing;
4340
4341 void centerprint_generic(int new_id, string strMessage, float duration, int countdown_num)
4342 {
4343         //printf("centerprint_generic(%d, '%s^7', %d, %d);\n", new_id, strMessage, duration, countdown_num);
4344         int i, j;
4345
4346         if(strMessage == "" && new_id == 0)
4347                 return;
4348
4349         // strip trailing newlines
4350         j = strlen(strMessage) - 1;
4351         while(substring(strMessage, j, 1) == "\n" && j >= 0)
4352                 --j;
4353         if (j < strlen(strMessage) - 1)
4354                 strMessage = substring(strMessage, 0, j + 1);
4355
4356         if(strMessage == "" && new_id == 0)
4357                 return;
4358
4359         // strip leading newlines
4360         j = 0;
4361         while(substring(strMessage, j, 1) == "\n" && j < strlen(strMessage))
4362                 ++j;
4363         if (j > 0)
4364                 strMessage = substring(strMessage, j, strlen(strMessage) - j);
4365
4366         if(strMessage == "" && new_id == 0)
4367                 return;
4368
4369         if (!centerprint_showing)
4370                 centerprint_showing = true;
4371
4372         for (i=0, j=cpm_index; i<CENTERPRINT_MAX_MSGS; ++i, ++j)
4373         {
4374                 if (j == CENTERPRINT_MAX_MSGS)
4375                         j = 0;
4376                 if (new_id && new_id == centerprint_msgID[j])
4377                 {
4378                         if (strMessage == "" && centerprint_messages[j] != "" && centerprint_countdown_num[j] == 0)
4379                         {
4380                                 // fade out the current msg (duration and countdown_num are ignored)
4381                                 centerprint_time[j] = min(5, autocvar_hud_panel_centerprint_fade_out);
4382                                 if (centerprint_expire_time[j] > time + min(5, autocvar_hud_panel_centerprint_fade_out) || centerprint_expire_time[j] < time)
4383                                         centerprint_expire_time[j] = time + min(5, autocvar_hud_panel_centerprint_fade_out);
4384                                 return;
4385                         }
4386                         break; // found a msg with the same id, at position j
4387                 }
4388         }
4389
4390         if (i == CENTERPRINT_MAX_MSGS)
4391         {
4392                 // a msg with the same id was not found, add the msg at the next position
4393                 --cpm_index;
4394                 if (cpm_index == -1)
4395                         cpm_index = CENTERPRINT_MAX_MSGS - 1;
4396                 j = cpm_index;
4397         }
4398         if(centerprint_messages[j])
4399                 strunzone(centerprint_messages[j]);
4400         centerprint_messages[j] = strzone(strMessage);
4401         centerprint_msgID[j] = new_id;
4402         if (duration < 0)
4403         {
4404                 centerprint_time[j] = -1;
4405                 centerprint_expire_time[j] = time;
4406         }
4407         else
4408         {
4409                 if(duration == 0)
4410                         duration = max(1, autocvar_hud_panel_centerprint_time);
4411                 centerprint_time[j] = duration;
4412                 centerprint_expire_time[j] = time + duration;
4413         }
4414         centerprint_countdown_num[j] = countdown_num;
4415 }
4416
4417 void centerprint_hud(string strMessage)
4418 {
4419         centerprint_generic(0, strMessage, autocvar_hud_panel_centerprint_time, 0);
4420 }
4421
4422 void reset_centerprint_messages(void)
4423 {
4424         int i;
4425         for (i=0; i<CENTERPRINT_MAX_MSGS; ++i)
4426         {
4427                 centerprint_expire_time[i] = 0;
4428                 centerprint_time[i] = 1;
4429                 centerprint_msgID[i] = 0;
4430                 if(centerprint_messages[i])
4431                         strunzone(centerprint_messages[i]);
4432                 centerprint_messages[i] = string_null;
4433         }
4434 }
4435 float hud_configure_cp_generation_time;
4436 void HUD_CenterPrint (void)
4437 {
4438         if(!autocvar__hud_configure)
4439         {
4440                 if(!autocvar_hud_panel_centerprint) return;
4441
4442                 if(hud_configure_prev)
4443                         reset_centerprint_messages();
4444         }
4445         else
4446         {
4447                 if(!hud_configure_prev)
4448                         reset_centerprint_messages();
4449                 if (time > hud_configure_cp_generation_time)
4450                 {
4451                         if(highlightedPanel == HUD_PANEL(CENTERPRINT))
4452                         {
4453                                 float r;
4454                                 r = random();
4455                                 if (r > 0.8)
4456                                         centerprint_generic(floor(r*1000), strcat(sprintf("^3Countdown message at time %s", seconds_tostring(time)), ", seconds left: ^COUNT"), 1, 10);
4457                                 else if (r > 0.55)
4458                                         centerprint_generic(0, sprintf("^1Multiline message at time %s that\n^1lasts longer than normal", seconds_tostring(time)), 20, 0);
4459                                 else
4460                                         centerprint_hud(sprintf("Message at time %s", seconds_tostring(time)));
4461                                 hud_configure_cp_generation_time = time + 1 + random()*4;
4462                         }
4463                         else
4464                         {
4465                                 centerprint_generic(0, sprintf("Centerprint message", seconds_tostring(time)), 10, 0);
4466                                 hud_configure_cp_generation_time = time + 10 - random()*3;
4467                         }
4468                 }
4469         }
4470
4471         // this panel fades only when the menu does
4472         float hud_fade_alpha_save = 0;
4473         if(scoreboard_fade_alpha)
4474         {
4475                 hud_fade_alpha_save = hud_fade_alpha;
4476                 hud_fade_alpha = 1 - autocvar__menu_alpha;
4477         }
4478         HUD_Panel_UpdateCvars();
4479
4480         if ( HUD_Radar_Clickable() )
4481         {
4482                 if (hud_panel_radar_bottom >= 0.96 * vid_conheight)
4483                         return;
4484
4485                 panel_pos = eY * hud_panel_radar_bottom + eX * 0.5 * (vid_conwidth - panel_size_x);
4486                 panel_size_y = min(panel_size_y, vid_conheight - hud_panel_radar_bottom);
4487         }
4488         else if(scoreboard_fade_alpha)
4489         {
4490                 hud_fade_alpha = hud_fade_alpha_save;
4491
4492                 // move the panel below the scoreboard
4493                 if (scoreboard_bottom >= 0.96 * vid_conheight)
4494                         return;
4495                 vector target_pos;
4496
4497                 target_pos = eY * scoreboard_bottom + eX * 0.5 * (vid_conwidth - panel_size.x);
4498
4499                 if(target_pos.y > panel_pos.y)
4500                 {
4501                         panel_pos = panel_pos + (target_pos - panel_pos) * sqrt(scoreboard_fade_alpha);
4502                         panel_size.y = min(panel_size.y, vid_conheight - scoreboard_bottom);
4503                 }
4504         }
4505
4506         HUD_Panel_DrawBg(1);
4507
4508         if (!centerprint_showing)
4509                 return;
4510
4511         if(panel_bg_padding)
4512         {
4513                 panel_pos += '1 1 0' * panel_bg_padding;
4514                 panel_size -= '2 2 0' * panel_bg_padding;
4515         }
4516
4517         int entries;
4518         float height;
4519         vector fontsize;
4520         // entries = bound(1, floor(CENTERPRINT_MAX_ENTRIES * 4 * panel_size_y/panel_size_x), CENTERPRINT_MAX_ENTRIES);
4521         // height = panel_size_y/entries;
4522         // fontsize = '1 1 0' * height;
4523         height = vid_conheight/50 * autocvar_hud_panel_centerprint_fontscale;
4524         fontsize = '1 1 0' * height;
4525         entries = bound(1, floor(panel_size.y/height), CENTERPRINT_MAX_ENTRIES);
4526
4527         int i, j, k, n, g;
4528         float a, sz, align, current_msg_posY = 0, msg_size;
4529         vector pos;
4530         string ts;
4531         bool all_messages_expired = true;
4532
4533         pos = panel_pos;
4534         if (autocvar_hud_panel_centerprint_flip)
4535                 pos.y += panel_size.y;
4536         align = bound(0, autocvar_hud_panel_centerprint_align, 1);
4537         for (g=0, i=0, j=cpm_index; i<CENTERPRINT_MAX_MSGS; ++i, ++j)
4538         {
4539                 if (j == CENTERPRINT_MAX_MSGS)
4540                         j = 0;
4541                 if (centerprint_expire_time[j] <= time)
4542                 {
4543                         if (centerprint_countdown_num[j] && centerprint_time[j] > 0)
4544                         {
4545                                 centerprint_countdown_num[j] = centerprint_countdown_num[j] - 1;
4546                                 if (centerprint_countdown_num[j] == 0)
4547                                         continue;
4548                                 centerprint_expire_time[j] = centerprint_expire_time[j] + centerprint_time[j];
4549                         }
4550                         else if(centerprint_time[j] != -1)
4551                                 continue;
4552                 }
4553
4554                 all_messages_expired = false;
4555
4556                 // fade the centerprint_hud in/out
4557                 if(centerprint_time[j] < 0)  // Expired but forced. Expire time is the fade-in time.
4558                         a = (time - centerprint_expire_time[j]) / max(0.0001, autocvar_hud_panel_centerprint_fade_in);
4559                 else if(centerprint_expire_time[j] - autocvar_hud_panel_centerprint_fade_out > time)  // Regularily printed. Not fading out yet.
4560                         a = (time - (centerprint_expire_time[j] - centerprint_time[j])) / max(0.0001, autocvar_hud_panel_centerprint_fade_in);
4561                 else // Expiring soon, so fade it out.
4562                         a = (centerprint_expire_time[j] - time) / max(0.0001, autocvar_hud_panel_centerprint_fade_out);
4563
4564                 // while counting down show it anyway in order to hold the current message position
4565                 if (a <= 0.5/255.0 && centerprint_countdown_num[j] == 0)  // Guaranteed invisible - don't show.
4566                         continue;
4567                 if (a > 1)
4568                         a = 1;
4569
4570                 // set the size from fading in/out before subsequent fading
4571                 sz = autocvar_hud_panel_centerprint_fade_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_minfontsize);
4572
4573                 // also fade it based on positioning
4574                 if(autocvar_hud_panel_centerprint_fade_subsequent)
4575                 {
4576                         a = a * bound(autocvar_hud_panel_centerprint_fade_subsequent_passone_minalpha, (1 - (g / max(1, autocvar_hud_panel_centerprint_fade_subsequent_passone))), 1); // pass one: all messages after the first have half theAlpha
4577                         a = a * bound(autocvar_hud_panel_centerprint_fade_subsequent_passtwo_minalpha, (1 - (g / max(1, autocvar_hud_panel_centerprint_fade_subsequent_passtwo))), 1); // pass two: after that, gradually lower theAlpha even more for each message
4578                 }
4579                 a *= panel_fg_alpha;
4580
4581                 // finally set the size based on the new theAlpha from subsequent fading
4582                 sz = sz * (autocvar_hud_panel_centerprint_fade_subsequent_minfontsize + a * (1 - autocvar_hud_panel_centerprint_fade_subsequent_minfontsize));
4583                 drawfontscale = sz * '1 1 0';
4584
4585                 if (centerprint_countdown_num[j])
4586                         n = tokenizebyseparator(strreplace("^COUNT", count_seconds(centerprint_countdown_num[j]), centerprint_messages[j]), "\n");
4587                 else
4588                         n = tokenizebyseparator(centerprint_messages[j], "\n");
4589
4590                 if (autocvar_hud_panel_centerprint_flip)
4591                 {
4592                         // check if the message can be entirely shown
4593                         for(k = 0; k < n; ++k)
4594                         {
4595                                 getWrappedLine_remaining = argv(k);
4596                                 while(getWrappedLine_remaining)
4597                                 {
4598                                         ts = getWrappedLine(panel_size.x * sz, fontsize, stringwidth_colors);
4599                                         if (ts != "")
4600                                                 pos.y -= fontsize.y;
4601                                         else
4602                                                 pos.y -= fontsize.y * CENTERPRINT_SPACING/2;
4603                                 }
4604                         }
4605                         current_msg_posY = pos.y; // save starting pos (first line) of the current message
4606                 }
4607
4608                 msg_size = pos.y;
4609                 for(k = 0; k < n; ++k)
4610                 {
4611                         getWrappedLine_remaining = argv(k);
4612                         while(getWrappedLine_remaining)
4613                         {
4614                                 ts = getWrappedLine(panel_size.x * sz, fontsize, stringwidth_colors);
4615                                 if (ts != "")
4616                                 {
4617                                         if (align)
4618                                                 pos.x = panel_pos.x + (panel_size.x - stringwidth(ts, true, fontsize)) * align;
4619                                         if (a > 0.5/255.0)  // Otherwise guaranteed invisible - don't show. This is checked a second time after some multiplications with other factors were done so temporary changes of these cannot cause flicker.
4620                                                 drawcolorcodedstring(pos + eY * 0.5 * (1 - sz) * fontsize.y, ts, fontsize, a, DRAWFLAG_NORMAL);
4621                                         pos.y += fontsize.y;
4622                                 }
4623                                 else
4624                                         pos.y += fontsize.y * CENTERPRINT_SPACING/2;
4625                         }
4626                 }
4627
4628                 ++g; // move next position number up
4629
4630                 msg_size = pos.y - msg_size;
4631                 if (autocvar_hud_panel_centerprint_flip)
4632                 {
4633                         pos.y = current_msg_posY - CENTERPRINT_SPACING * fontsize.y;
4634                         if (a < 1 && centerprint_msgID[j] == 0) // messages with id can be replaced just after they are faded out, so never move over them the next messages
4635                                 pos.y += (msg_size + CENTERPRINT_SPACING * fontsize.y) * (1 - sqrt(sz));
4636
4637                         if (pos.y < panel_pos.y) // check if the next message can be shown
4638                         {
4639                                 drawfontscale = '1 1 0';
4640                                 return;
4641                         }
4642                 }
4643                 else
4644                 {
4645                         pos.y += CENTERPRINT_SPACING * fontsize.y;
4646                         if (a < 1 && centerprint_msgID[j] == 0) // messages with id can be replaced just after they are faded out, so never move over them the next messages
4647                                 pos.y -= (msg_size + CENTERPRINT_SPACING * fontsize.y) * (1 - sqrt(sz));
4648
4649                         if(pos.y > panel_pos.y + panel_size.y - fontsize.y) // check if the next message can be shown
4650                         {
4651                                 drawfontscale = '1 1 0';
4652                                 return;
4653                         }
4654                 }
4655         }
4656         drawfontscale = '1 1 0';
4657         if (all_messages_expired)
4658         {
4659                 centerprint_showing = false;
4660                 reset_centerprint_messages();
4661         }
4662 }
4663
4664
4665 // Minigame
4666 //
4667 #include "../common/minigames/cl_minigames_hud.qc"
4668
4669
4670 // QuickMenu (#23)
4671 //
4672 #include "quickmenu.qc"
4673
4674
4675 /*
4676 ==================
4677 Main HUD system
4678 ==================
4679 */
4680
4681 void HUD_Vehicle()
4682 {
4683         if(autocvar__hud_configure) return;
4684         if(intermission == 2) return;
4685
4686         if(hud == HUD_BUMBLEBEE_GUN)
4687                 CSQC_BUMBLE_GUN_HUD();
4688         else {
4689                 Vehicle info = get_vehicleinfo(hud);
4690                 info.vr_hud(info);
4691         }
4692 }
4693
4694 bool HUD_Panel_CheckFlags(int showflags)
4695 {
4696         if ( HUD_Minigame_Showpanels() )
4697                 return showflags & PANEL_SHOW_MINIGAME;
4698         if(intermission == 2)
4699                 return showflags & PANEL_SHOW_MAPVOTE;
4700         return showflags & PANEL_SHOW_MAINGAME;
4701 }
4702
4703 void HUD_Panel_Draw(entity panent)
4704 {
4705         panel = panent;
4706         if(autocvar__hud_configure)
4707         {
4708                 if(panel.panel_configflags & PANEL_CONFIG_MAIN)
4709                         panel.panel_draw();
4710         }
4711         else if(HUD_Panel_CheckFlags(panel.panel_showflags))
4712                 panel.panel_draw();
4713 }
4714
4715 void HUD_Reset(void)
4716 {
4717         // reset gametype specific icons
4718         if(gametype == MAPINFO_TYPE_CTF)
4719                 HUD_Mod_CTF_Reset();
4720 }
4721
4722 void HUD_Main(void)
4723 {
4724         int i;
4725         // global hud theAlpha fade
4726         if(menu_enabled == 1)
4727                 hud_fade_alpha = 1;
4728         else
4729                 hud_fade_alpha = (1 - autocvar__menu_alpha);
4730
4731         if(scoreboard_fade_alpha)
4732                 hud_fade_alpha = (1 - scoreboard_fade_alpha);
4733
4734         HUD_Configure_Frame();
4735
4736         // panels that we want to be active together with the scoreboard
4737         // they must fade only when the menu does
4738         if(scoreboard_fade_alpha == 1)
4739         {
4740                 HUD_Panel_Draw(HUD_PANEL(CENTERPRINT));
4741                 return;
4742         }
4743
4744         if(!autocvar__hud_configure && !hud_fade_alpha)
4745         {
4746                 hud_fade_alpha = 1;
4747                 HUD_Panel_Draw(HUD_PANEL(VOTE));
4748                 hud_fade_alpha = 0;
4749                 return;
4750         }
4751
4752         // Drawing stuff
4753         if (hud_skin_prev != autocvar_hud_skin)
4754         {
4755                 if (hud_skin_path)
4756                         strunzone(hud_skin_path);
4757                 hud_skin_path = strzone(strcat("gfx/hud/", autocvar_hud_skin));
4758                 if (hud_skin_prev)
4759                         strunzone(hud_skin_prev);
4760                 hud_skin_prev = strzone(autocvar_hud_skin);
4761         }
4762
4763         // draw the dock
4764         if(autocvar_hud_dock != "" && autocvar_hud_dock != "0")
4765         {
4766                 int f;
4767                 vector color;
4768                 float hud_dock_color_team = autocvar_hud_dock_color_team;
4769                 if((teamplay) && hud_dock_color_team) {
4770                         if(autocvar__hud_configure && myteam == NUM_SPECTATOR)
4771                                 color = '1 0 0' * hud_dock_color_team;
4772                         else
4773                                 color = myteamcolors * hud_dock_color_team;
4774                 }
4775                 else if(autocvar_hud_configure_teamcolorforced && autocvar__hud_configure && hud_dock_color_team) {
4776                         color = '1 0 0' * hud_dock_color_team;
4777                 }
4778                 else
4779                 {
4780                         string hud_dock_color = autocvar_hud_dock_color;
4781                         if(hud_dock_color == "shirt") {
4782                                 f = stof(getplayerkeyvalue(current_player, "colors"));
4783                                 color = colormapPaletteColor(floor(f / 16), 0);
4784                         }
4785                         else if(hud_dock_color == "pants") {
4786                                 f = stof(getplayerkeyvalue(current_player, "colors"));
4787                                 color = colormapPaletteColor(f % 16, 1);
4788                         }
4789                         else
4790                                 color = stov(hud_dock_color);
4791                 }
4792
4793                 string pic;
4794                 pic = strcat(hud_skin_path, "/", autocvar_hud_dock);
4795                 if(precache_pic(pic) == "") {
4796                         pic = strcat(hud_skin_path, "/dock_medium");
4797                         if(precache_pic(pic) == "") {
4798                                 pic = "gfx/hud/default/dock_medium";
4799                         }
4800                 }
4801                 drawpic('0 0 0', pic, eX * vid_conwidth + eY * vid_conheight, color, autocvar_hud_dock_alpha * hud_fade_alpha, DRAWFLAG_NORMAL); // no aspect ratio forcing on dock...
4802         }
4803
4804         // cache the panel order into the panel_order array
4805         if(autocvar__hud_panelorder != hud_panelorder_prev) {
4806                 for(i = 0; i < hud_panels_COUNT; ++i)
4807                         panel_order[i] = -1;
4808                 string s = "";
4809                 int p_num;
4810                 bool warning = false;
4811                 int argc = tokenize_console(autocvar__hud_panelorder);
4812                 if (argc > hud_panels_COUNT)
4813                         warning = true;
4814                 //first detect wrong/missing panel numbers
4815                 for(i = 0; i < hud_panels_COUNT; ++i) {
4816                         p_num = stoi(argv(i));
4817                         if (p_num >= 0 && p_num < hud_panels_COUNT) { //correct panel number?
4818                                 if (panel_order[p_num] == -1) //found for the first time?
4819                                         s = strcat(s, ftos(p_num), " ");
4820                                 panel_order[p_num] = 1; //mark as found
4821                         }
4822                         else
4823                                 warning = true;
4824                 }
4825                 for(i = 0; i < hud_panels_COUNT; ++i) {
4826                         if (panel_order[i] == -1) {
4827                                 warning = true;
4828                                 s = strcat(s, ftos(i), " "); //add missing panel number
4829                         }
4830                 }
4831                 if (warning)
4832                         LOG_TRACE("Automatically fixed wrong/missing panel numbers in _hud_panelorder\n");
4833
4834                 cvar_set("_hud_panelorder", s);
4835                 if(hud_panelorder_prev)
4836                         strunzone(hud_panelorder_prev);
4837                 hud_panelorder_prev = strzone(s);
4838
4839                 //now properly set panel_order
4840                 tokenize_console(s);
4841                 for(i = 0; i < hud_panels_COUNT; ++i) {
4842                         panel_order[i] = stof(argv(i));
4843                 }
4844         }
4845
4846         hud_draw_maximized = 0;
4847         // draw panels in the order specified by panel_order array
4848         for(i = hud_panels_COUNT - 1; i >= 0; --i)
4849                 HUD_Panel_Draw(hud_panels[panel_order[i]]);
4850
4851         HUD_Vehicle();
4852
4853         hud_draw_maximized = 1; // panels that may be maximized must check this var
4854         // draw maximized panels on top
4855         if(hud_panel_radar_maximized)
4856                 HUD_Panel_Draw(HUD_PANEL(RADAR));
4857         if(autocvar__con_chat_maximized)
4858                 HUD_Panel_Draw(HUD_PANEL(CHAT));
4859         if(hud_panel_quickmenu)
4860                 HUD_Panel_Draw(HUD_PANEL(QUICKMENU));
4861
4862         if (scoreboard_active || intermission == 2)
4863                 HUD_Reset();
4864
4865         HUD_Configure_PostDraw();
4866
4867         hud_configure_prev = autocvar__hud_configure;
4868 }