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