Merge branch 'Mario/teams_bitflag' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / modicons.qc
1 #include "modicons.qh"
2
3 #include <common/mapinfo.qh>
4 #include <common/ent_cs.qh>
5 #include <server/mutators/mutator/gamemode_ctf.qh> // TODO: remove
6
7 // Mod icons panel (#10)
8
9 bool mod_active; // is there any active mod icon?
10
11 void DrawCAItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
12 {
13     TC(int, layout); TC(int, i);
14         int stat = -1;
15         string pic = "";
16         vector color = '0 0 0';
17         switch(i)
18         {
19                 case 0:
20                         stat = STAT(REDALIVE);
21                         pic = "player_red.tga";
22                         color = '1 0 0';
23                         break;
24                 case 1:
25                         stat = STAT(BLUEALIVE);
26                         pic = "player_blue.tga";
27                         color = '0 0 1';
28                         break;
29                 case 2:
30                         stat = STAT(YELLOWALIVE);
31                         pic = "player_yellow.tga";
32                         color = '1 1 0';
33                         break;
34                 default:
35                 case 3:
36                         stat = STAT(PINKALIVE);
37                         pic = "player_pink.tga";
38                         color = '1 0 1';
39                         break;
40         }
41
42         if(mySize.x/mySize.y > aspect_ratio)
43         {
44                 i = aspect_ratio * mySize.y;
45                 myPos.x = myPos.x + (mySize.x - i) / 2;
46                 mySize.x = i;
47         }
48         else
49         {
50                 i = 1/aspect_ratio * mySize.x;
51                 myPos.y = myPos.y + (mySize.y - i) / 2;
52                 mySize.y = i;
53         }
54
55         if(layout)
56         {
57                 drawpic_aspect_skin(myPos, pic, eX * 0.7 * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
58                 drawstring_aspect(myPos + eX * 0.7 * mySize.x, ftos(stat), eX * 0.3 * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
59         }
60         else
61                 drawstring_aspect(myPos, ftos(stat), mySize, color, panel_fg_alpha, DRAWFLAG_NORMAL);
62 }
63
64 // Clan Arena and Freeze Tag HUD modicons
65 void HUD_Mod_CA(vector myPos, vector mySize)
66 {
67         mod_active = 1; // required in each mod function that always shows something
68
69         int layout;
70         if(gametype == MAPINFO_TYPE_CA)
71                 layout = autocvar_hud_panel_modicons_ca_layout;
72         else //if(gametype == MAPINFO_TYPE_FREEZETAG)
73                 layout = autocvar_hud_panel_modicons_freezetag_layout;
74         int rows, columns;
75         float aspect_ratio;
76         aspect_ratio = (layout) ? 2 : 1;
77         rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
78         columns = ceil(team_count/rows);
79
80         int i;
81         float row = 0, column = 0;
82         vector pos, itemSize;
83         itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
84         for(i=0; i<team_count; ++i)
85         {
86                 pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
87
88                 DrawCAItem(pos, itemSize, aspect_ratio, layout, i);
89
90                 ++row;
91                 if(row >= rows)
92                 {
93                         row = 0;
94                         ++column;
95                 }
96         }
97 }
98
99 // CTF HUD modicon section
100 int redflag_prevframe, blueflag_prevframe, yellowflag_prevframe, pinkflag_prevframe, neutralflag_prevframe; // status during previous frame
101 int redflag_prevstatus, blueflag_prevstatus, yellowflag_prevstatus, pinkflag_prevstatus, neutralflag_prevstatus; // last remembered status
102 float redflag_statuschange_time, blueflag_statuschange_time, yellowflag_statuschange_time, pinkflag_statuschange_time, neutralflag_statuschange_time; // time when the status changed
103
104 void HUD_Mod_CTF_Reset()
105 {
106         redflag_prevstatus = blueflag_prevstatus = yellowflag_prevstatus = pinkflag_prevstatus = neutralflag_prevstatus = 0;
107         redflag_prevframe = blueflag_prevframe = yellowflag_prevframe = pinkflag_prevframe = neutralflag_prevframe = 0;
108         redflag_statuschange_time = blueflag_statuschange_time = yellowflag_statuschange_time = pinkflag_statuschange_time = neutralflag_statuschange_time = 0;
109 }
110
111 int autocvar__teams_available;
112 void HUD_Mod_CTF(vector pos, vector mySize)
113 {
114         vector redflag_pos, blueflag_pos, yellowflag_pos, pinkflag_pos, neutralflag_pos;
115         vector flag_size;
116         float f; // every function should have that
117
118         int redflag, blueflag, yellowflag, pinkflag, neutralflag; // current status
119         float redflag_statuschange_elapsedtime = 0, blueflag_statuschange_elapsedtime = 0, yellowflag_statuschange_elapsedtime = 0, pinkflag_statuschange_elapsedtime = 0, neutralflag_statuschange_elapsedtime = 0; // time since the status changed
120         bool ctf_oneflag; // one-flag CTF mode enabled/disabled
121         bool ctf_stalemate; // currently in stalemate
122         int stat_items = STAT(CTF_FLAGSTATUS);
123         float fs, fs2, fs3, size1, size2;
124         vector e1, e2;
125
126         int nteams = autocvar__teams_available;
127
128         redflag = (stat_items/CTF_RED_FLAG_TAKEN) & 3;
129         blueflag = (stat_items/CTF_BLUE_FLAG_TAKEN) & 3;
130         yellowflag = (stat_items/CTF_YELLOW_FLAG_TAKEN) & 3;
131         pinkflag = (stat_items/CTF_PINK_FLAG_TAKEN) & 3;
132         neutralflag = (stat_items/CTF_NEUTRAL_FLAG_TAKEN) & 3;
133
134         ctf_oneflag = (stat_items & CTF_FLAG_NEUTRAL);
135
136         ctf_stalemate = (stat_items & CTF_STALEMATE);
137
138         mod_active = (redflag || blueflag || yellowflag || pinkflag || neutralflag || (stat_items & CTF_SHIELDED));
139
140         if (autocvar__hud_configure) {
141                 redflag = 1;
142                 blueflag = 2;
143                 if (nteams & BIT(2))
144                         yellowflag = 2;
145                 if (nteams & BIT(3))
146                         pinkflag = 3;
147                 ctf_oneflag = neutralflag = 0; // disable neutral flag in hud editor?
148         }
149
150         // when status CHANGES, set old status into prevstatus and current status into status
151         #define X(team) MACRO_BEGIN {                                                                                                   \
152                 if (team##flag != team##flag_prevframe) {                                                                       \
153                 team##flag_statuschange_time = time;                                                                    \
154                 team##flag_prevstatus = team##flag_prevframe;                                                   \
155                 team##flag_prevframe = team##flag;                                                                              \
156         }                                                                                                                                                       \
157         team##flag_statuschange_elapsedtime = time - team##flag_statuschange_time;      \
158     } MACRO_END
159         X(red);
160         X(blue);
161         X(yellow);
162         X(pink);
163         X(neutral);
164         #undef X
165
166         const float BLINK_FACTOR = 0.15;
167         const float BLINK_BASE = 0.85;
168         // note:
169         //   RMS = sqrt(BLINK_BASE^2 + 0.5 * BLINK_FACTOR^2)
170         // thus
171         //   BLINK_BASE = sqrt(RMS^2 - 0.5 * BLINK_FACTOR^2)
172         // ensure RMS == 1
173         const float BLINK_FREQ = 5; // circle frequency, = 2*pi*frequency in hertz
174
175         #define X(team, cond) \
176         string team##_icon = string_null, team##_icon_prevstatus = string_null; \
177         int team##_alpha, team##_alpha_prevstatus; \
178         team##_alpha = team##_alpha_prevstatus = 1; \
179         MACRO_BEGIN { \
180                 switch (team##flag) { \
181                         case 1: team##_icon = "flag_" #team "_taken"; break; \
182                         case 2: team##_icon = "flag_" #team "_lost"; break; \
183                         case 3: team##_icon = "flag_" #team "_carrying"; team##_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
184                         default: \
185                                 if ((stat_items & CTF_SHIELDED) && (cond)) { \
186                                         team##_icon = "flag_" #team "_shielded"; \
187                                 } else { \
188                                         team##_icon = string_null; \
189                                 } \
190                                 break; \
191                 } \
192                 switch (team##flag_prevstatus) { \
193                         case 1: team##_icon_prevstatus = "flag_" #team "_taken"; break; \
194                         case 2: team##_icon_prevstatus = "flag_" #team "_lost"; break; \
195                         case 3: team##_icon_prevstatus = "flag_" #team "_carrying"; team##_alpha_prevstatus = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ); break; \
196                         default: \
197                                 if (team##flag == 3) { \
198                                         team##_icon_prevstatus = "flag_" #team "_carrying"; /* make it more visible */\
199                                 } else if((stat_items & CTF_SHIELDED) && (cond)) { \
200                                         team##_icon_prevstatus = "flag_" #team "_shielded"; \
201                                 } else { \
202                                         team##_icon_prevstatus = string_null; \
203                                 } \
204                                 break; \
205                 } \
206         } MACRO_END
207         X(red, myteam != NUM_TEAM_1 && (nteams & BIT(0)));
208         X(blue, myteam != NUM_TEAM_2 && (nteams & BIT(1)));
209         X(yellow, myteam != NUM_TEAM_3 && (nteams & BIT(2)));
210         X(pink, myteam != NUM_TEAM_4 && (nteams & BIT(3)));
211         X(neutral, ctf_oneflag);
212         #undef X
213
214         int tcount = 2;
215         if(nteams & BIT(2))
216                 tcount = 3;
217         if(nteams & BIT(3))
218                 tcount = 4;
219
220         if (ctf_oneflag) {
221                 // hacky, but these aren't needed
222                 red_icon = red_icon_prevstatus = blue_icon = blue_icon_prevstatus = yellow_icon = yellow_icon_prevstatus = pink_icon = pink_icon_prevstatus = string_null;
223                 fs = fs2 = fs3 = 1;
224         } else switch (tcount) {
225                 default:
226                 case 2: fs = 0.5; fs2 = 0.5; fs3 = 0.5; break;
227                 case 3: fs = 1; fs2 = 0.35; fs3 = 0.35; break;
228                 case 4: fs = 0.75; fs2 = 0.25; fs3 = 0.5; break;
229         }
230
231         if (mySize_x > mySize_y) {
232                 size1 = mySize_x;
233                 size2 = mySize_y;
234                 e1 = eX;
235                 e2 = eY;
236         } else {
237                 size1 = mySize_y;
238                 size2 = mySize_x;
239                 e1 = eY;
240                 e2 = eX;
241         }
242
243         switch (myteam) {
244                 default:
245                 case NUM_TEAM_1: {
246                         redflag_pos = pos;
247                         blueflag_pos = pos + eX * fs2 * size1;
248                         yellowflag_pos = pos - eX * fs2 * size1;
249                         pinkflag_pos = pos + eX * fs3 * size1;
250                         break;
251                 }
252                 case NUM_TEAM_2: {
253                         redflag_pos = pos + eX * fs2 * size1;
254                         blueflag_pos = pos;
255                         yellowflag_pos = pos - eX * fs2 * size1;
256                         pinkflag_pos = pos + eX * fs3 * size1;
257                         break;
258                 }
259                 case NUM_TEAM_3: {
260                         redflag_pos = pos + eX * fs3 * size1;
261                         blueflag_pos = pos - eX * fs2 * size1;
262                         yellowflag_pos = pos;
263                         pinkflag_pos = pos + eX * fs2 * size1;
264                         break;
265                 }
266                 case NUM_TEAM_4: {
267                         redflag_pos = pos - eX * fs2 * size1;
268                         blueflag_pos = pos + eX * fs3 * size1;
269                         yellowflag_pos = pos + eX * fs2 * size1;
270                         pinkflag_pos = pos;
271                         break;
272                 }
273         }
274         neutralflag_pos = pos;
275         flag_size = e1 * fs * size1 + e2 * size2;
276
277         #define X(team) MACRO_BEGIN { \
278                 f = bound(0, team##flag_statuschange_elapsedtime * 2, 1); \
279                 if (team##_icon && ctf_stalemate) \
280                         drawpic_aspect_skin(team##flag_pos, "flag_stalemate", flag_size, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL); \
281                 if (team##_icon_prevstatus && f < 1) \
282                         drawpic_aspect_skin_expanding(team##flag_pos, team##_icon_prevstatus, flag_size, '1 1 1', panel_fg_alpha * team##_alpha_prevstatus, DRAWFLAG_NORMAL, f); \
283                 if (team##_icon) \
284                         drawpic_aspect_skin(team##flag_pos, team##_icon, flag_size, '1 1 1', panel_fg_alpha * team##_alpha * f, DRAWFLAG_NORMAL); \
285         } MACRO_END
286         X(red);
287         X(blue);
288         X(yellow);
289         X(pink);
290         X(neutral);
291         #undef X
292 }
293
294 // Keyhunt HUD modicon section
295 vector KH_SLOTS[4];
296
297 void HUD_Mod_KH(vector pos, vector mySize)
298 {
299         mod_active = 1; // keyhunt should never hide the mod icons panel
300
301         // Read current state
302
303         int state = STAT(KH_KEYS);
304         int i, key_state;
305         int all_keys, team1_keys, team2_keys, team3_keys, team4_keys, dropped_keys, carrying_keys;
306         all_keys = team1_keys = team2_keys = team3_keys = team4_keys = dropped_keys = carrying_keys = 0;
307
308         for(i = 0; i < 4; ++i)
309         {
310                 key_state = (bitshift(state, i * -5) & 31) - 1;
311
312                 if(key_state == -1)
313                         continue;
314
315                 if(key_state == 30)
316                 {
317                         ++carrying_keys;
318                         key_state = myteam;
319                 }
320
321                 switch(key_state)
322                 {
323                         case NUM_TEAM_1: ++team1_keys; break;
324                         case NUM_TEAM_2: ++team2_keys; break;
325                         case NUM_TEAM_3: ++team3_keys; break;
326                         case NUM_TEAM_4: ++team4_keys; break;
327                         case 29: ++dropped_keys; break;
328                 }
329
330                 ++all_keys;
331         }
332
333         // Calculate slot measurements
334
335         vector slot_size;
336
337         if(all_keys == 4 && mySize.x * 0.5 < mySize.y && mySize.y * 0.5 < mySize.x)
338         {
339                 // Quadratic arrangement
340                 slot_size = eX * mySize.x * 0.5 + eY * mySize.y * 0.5;
341                 KH_SLOTS[0] = pos;
342                 KH_SLOTS[1] = pos + eX * slot_size.x;
343                 KH_SLOTS[2] = pos + eY * slot_size.y;
344                 KH_SLOTS[3] = pos + eX * slot_size.x + eY * slot_size.y;
345         }
346         else
347         {
348                 if(mySize.x > mySize.y)
349                 {
350                         // Horizontal arrangement
351                         slot_size = eX * mySize.x / all_keys + eY * mySize.y;
352                         for(i = 0; i < all_keys; ++i)
353                                 KH_SLOTS[i] = pos + eX * slot_size.x * i;
354                 }
355                 else
356                 {
357                         // Vertical arrangement
358                         slot_size = eX * mySize.x + eY * mySize.y / all_keys;
359                         for(i = 0; i < all_keys; ++i)
360                                 KH_SLOTS[i] = pos + eY * slot_size.y * i;
361                 }
362         }
363
364         // Make icons blink in case of RUN HERE
365
366         float blink = 0.6 + sin(2*M_PI*time) / 2.5; // Oscillate between 0.2 and 1
367         float alpha;
368         alpha = 1;
369
370         if(carrying_keys)
371                 switch(myteam)
372                 {
373                         case NUM_TEAM_1: if(team1_keys == all_keys) alpha = blink; break;
374                         case NUM_TEAM_2: if(team2_keys == all_keys) alpha = blink; break;
375                         case NUM_TEAM_3: if(team3_keys == all_keys) alpha = blink; break;
376                         case NUM_TEAM_4: if(team4_keys == all_keys) alpha = blink; break;
377                 }
378
379         // Draw icons
380
381         i = 0;
382
383         while(team1_keys--)
384                 if(myteam == NUM_TEAM_1 && carrying_keys)
385                 {
386                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
387                         --carrying_keys;
388                 }
389                 else
390                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_red_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
391
392         while(team2_keys--)
393                 if(myteam == NUM_TEAM_2 && carrying_keys)
394                 {
395                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
396                         --carrying_keys;
397                 }
398                 else
399                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_blue_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
400
401         while(team3_keys--)
402                 if(myteam == NUM_TEAM_3 && carrying_keys)
403                 {
404                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
405                         --carrying_keys;
406                 }
407                 else
408                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_yellow_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
409
410         while(team4_keys--)
411                 if(myteam == NUM_TEAM_4 && carrying_keys)
412                 {
413                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_carrying", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
414                         --carrying_keys;
415                 }
416                 else
417                         drawpic_aspect_skin(KH_SLOTS[i++], "kh_pink_taken", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
418
419         while(dropped_keys--)
420                 drawpic_aspect_skin(KH_SLOTS[i++], "kh_dropped", slot_size, '1 1 1', alpha, DRAWFLAG_NORMAL);
421 }
422
423 // Keepaway HUD mod icon
424 int kaball_prevstatus; // last remembered status
425 float kaball_statuschange_time; // time when the status changed
426
427 // we don't need to reset for keepaway since it immediately
428 // autocorrects prevstatus as to if the player has the ball or not
429
430 void HUD_Mod_Keepaway(vector pos, vector mySize)
431 {
432         mod_active = 1; // keepaway should always show the mod HUD
433
434         float BLINK_FACTOR = 0.15;
435         float BLINK_BASE = 0.85;
436         float BLINK_FREQ = 5;
437         float kaball_alpha = BLINK_BASE + BLINK_FACTOR * cos(time * BLINK_FREQ);
438
439         int stat_items = STAT(ITEMS);
440         int kaball = (stat_items/IT_KEY1) & 1;
441
442         if(kaball != kaball_prevstatus)
443         {
444                 kaball_statuschange_time = time;
445                 kaball_prevstatus = kaball;
446         }
447
448         vector kaball_pos, kaball_size;
449
450         if(mySize.x > mySize.y) {
451                 kaball_pos = pos + eX * 0.25 * mySize.x;
452                 kaball_size = eX * 0.5 * mySize.x + eY * mySize.y;
453         } else {
454                 kaball_pos = pos + eY * 0.25 * mySize.y;
455                 kaball_size = eY * 0.5 * mySize.y + eX * mySize.x;
456         }
457
458         float kaball_statuschange_elapsedtime = time - kaball_statuschange_time;
459         float f = bound(0, kaball_statuschange_elapsedtime*2, 1);
460
461         if(kaball_prevstatus && f < 1)
462                 drawpic_aspect_skin_expanding(kaball_pos, "keepawayball_carrying", kaball_size, '1 1 1', panel_fg_alpha * kaball_alpha, DRAWFLAG_NORMAL, f);
463
464         if(kaball)
465                 drawpic_aspect_skin(pos, "keepawayball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha * kaball_alpha * f, DRAWFLAG_NORMAL);
466 }
467
468
469 // Nexball HUD mod icon
470 void HUD_Mod_NexBall(vector pos, vector mySize)
471 {
472         float nb_pb_starttime, dt, p;
473         int stat_items;
474
475         stat_items = STAT(ITEMS);
476         nb_pb_starttime = STAT(NB_METERSTART);
477
478         if (stat_items & IT_KEY1)
479                 mod_active = 1;
480         else
481                 mod_active = 0;
482
483         //Manage the progress bar if any
484         if (nb_pb_starttime > 0)
485         {
486                 dt = (time - nb_pb_starttime) % nb_pb_period;
487                 // one period of positive triangle
488                 p = 2 * dt / nb_pb_period;
489                 if (p > 1)
490                         p = 2 - p;
491
492                 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);
493         }
494
495         if (stat_items & IT_KEY1)
496                 drawpic_aspect_skin(pos, "nexball_carrying", eX * mySize.x + eY * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
497 }
498
499 // Race/CTS HUD mod icons
500 float crecordtime_prev; // last remembered crecordtime
501 float crecordtime_change_time; // time when crecordtime last changed
502 float srecordtime_prev; // last remembered srecordtime
503 float srecordtime_change_time; // time when srecordtime last changed
504
505 float race_status_time;
506 int race_status_prev;
507 string race_status_name_prev;
508
509 // Check if the given name already exist in race rankings? In that case, where? (otherwise return 0)
510 int race_CheckName(string net_name)
511 {
512         int i;
513         for (i=RANKINGS_CNT-1;i>=0;--i)
514                 if(grecordholder[i] == net_name)
515                         return i+1;
516         return 0;
517 }
518
519 void race_showTime(string text, vector pos, vector timeText_ofs, float theTime, vector textSize, float f)
520 {
521         drawstring_aspect(pos, text, textSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
522         drawstring_aspect(pos + timeText_ofs, TIME_ENCODED_TOSTRING(theTime), textSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
523         if (f < 1) {
524                 drawstring_aspect_expanding(pos, text, textSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
525                 drawstring_aspect_expanding(pos + timeText_ofs, TIME_ENCODED_TOSTRING(theTime), textSize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL, f);
526         }
527 }
528
529 void HUD_Mod_Race(vector pos, vector mySize)
530 {
531         mod_active = 1; // race should never hide the mod icons panel
532         entity me;
533         me = playerslots[player_localnum];
534         float score;
535         score = me.(scores[ps_primary]);
536
537         if(!(scores_flags[ps_primary] & SFL_TIME) || teamplay) // race/cts record display on HUD
538                 return; // no records in the actual race
539
540         // clientside personal record
541         string rr;
542         if(gametype == MAPINFO_TYPE_CTS)
543                 rr = CTS_RECORD;
544         else
545                 rr = RACE_RECORD;
546         float t = stof(db_get(ClientProgsDB, strcat(shortmapname, rr, "time")));
547
548         if(score && (score < t || !t)) {
549                 db_put(ClientProgsDB, strcat(shortmapname, rr, "time"), ftos(score));
550                 if(autocvar_cl_autodemo_delete_keeprecords)
551                 {
552                         float f = autocvar_cl_autodemo_delete;
553                         f &= ~1;
554                         cvar_set("cl_autodemo_delete", ftos(f)); // don't delete demo with new record!
555                 }
556         }
557
558         if(t != crecordtime_prev) {
559                 crecordtime_prev = t;
560                 crecordtime_change_time = time;
561         }
562
563         vector textPos, medalPos;
564         float squareSize;
565         if(mySize.x > mySize.y) {
566                 // text on left side
567                 squareSize = min(mySize.y, mySize.x/2);
568                 textPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eY * 0.5 * (mySize.y - squareSize);
569                 medalPos = pos + eX * 0.5 * max(0, mySize.x/2 - squareSize) + eX * 0.5 * mySize.x + eY * 0.5 * (mySize.y - squareSize);
570         } else {
571                 // text on top
572                 squareSize = min(mySize.x, mySize.y/2);
573                 textPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eX * 0.5 * (mySize.x - squareSize);
574                 medalPos = pos + eY * 0.5 * max(0, mySize.y/2 - squareSize) + eY * 0.5 * mySize.y + eX * 0.5 * (mySize.x - squareSize);
575         }
576         vector textSize = eX * squareSize + eY * 0.25 * squareSize;
577
578         race_showTime(_("Personal best"), textPos, eY * 0.25 * squareSize, t, textSize, time - crecordtime_change_time);
579
580         // server record
581         t = race_server_record;
582         if(t != srecordtime_prev) {
583                 srecordtime_prev = t;
584                 srecordtime_change_time = time;
585         }
586
587         textPos += eY * 0.5 * squareSize;
588         race_showTime(_("Server best"), textPos, eY * 0.25 * squareSize, t, textSize, time - srecordtime_change_time);
589
590         if (race_status != race_status_prev || race_status_name != race_status_name_prev) {
591                 race_status_time = time + 5;
592                 race_status_prev = race_status;
593                 if (race_status_name_prev)
594                         strunzone(race_status_name_prev);
595                 race_status_name_prev = strzone(race_status_name);
596         }
597
598         // race "awards"
599         float a;
600         a = bound(0, race_status_time - time, 1);
601
602         string s;
603         s = textShortenToWidth(race_status_name, squareSize, '1 1 0' * 0.1 * squareSize, stringwidth_colors);
604
605         float rank;
606         if(race_status > 0)
607                 rank = race_CheckName(race_status_name);
608         else
609                 rank = 0;
610         string rankname;
611         rankname = count_ordinal(rank);
612
613         vector namepos;
614         namepos = medalPos + '0 0.8 0' * squareSize;
615         vector rankpos;
616         rankpos = medalPos + '0 0.15 0' * squareSize;
617
618         if(race_status == 0)
619                 drawpic_aspect_skin(medalPos, "race_newfail", '1 1 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
620         else if(race_status == 1) {
621                 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);
622                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
623                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
624         } else if(race_status == 2) {
625                 if(race_status_name == entcs_GetName(player_localnum) || !race_myrank || race_myrank < rank)
626                         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);
627                 else
628                         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);
629                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
630                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
631         } else if(race_status == 3) {
632                 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);
633                 drawcolorcodedstring_aspect(namepos, s, '1 0.2 0' * squareSize, panel_fg_alpha * a, DRAWFLAG_NORMAL);
634                 drawstring_aspect(rankpos, rankname, '1 0.15 0' * squareSize, '1 1 1', panel_fg_alpha * a, DRAWFLAG_NORMAL);
635         }
636
637         if (race_status_time - time <= 0) {
638                 race_status_prev = -1;
639                 race_status = -1;
640                 if(race_status_name)
641                         strunzone(race_status_name);
642                 race_status_name = string_null;
643                 if(race_status_name_prev)
644                         strunzone(race_status_name_prev);
645                 race_status_name_prev = string_null;
646         }
647 }
648
649 void DrawDomItem(vector myPos, vector mySize, float aspect_ratio, int layout, int i)
650 {
651     TC(int, layout); TC(int, i);
652         float stat = -1;
653         string pic = "";
654         vector color = '0 0 0';
655         switch(i)
656         {
657                 case 0:
658                         stat = STAT(DOM_PPS_RED);
659                         pic = "dom_icon_red";
660                         color = '1 0 0';
661                         break;
662                 case 1:
663                         stat = STAT(DOM_PPS_BLUE);
664                         pic = "dom_icon_blue";
665                         color = '0 0 1';
666                         break;
667                 case 2:
668                         stat = STAT(DOM_PPS_YELLOW);
669                         pic = "dom_icon_yellow";
670                         color = '1 1 0';
671                         break;
672                 default:
673                 case 3:
674                         stat = STAT(DOM_PPS_PINK);
675                         pic = "dom_icon_pink";
676                         color = '1 0 1';
677                         break;
678         }
679         float pps_ratio = stat / STAT(DOM_TOTAL_PPS);
680
681         if(mySize.x/mySize.y > aspect_ratio)
682         {
683                 i = aspect_ratio * mySize.y;
684                 myPos.x = myPos.x + (mySize.x - i) / 2;
685                 mySize.x = i;
686         }
687         else
688         {
689                 i = 1/aspect_ratio * mySize.x;
690                 myPos.y = myPos.y + (mySize.y - i) / 2;
691                 mySize.y = i;
692         }
693
694         if (layout) // show text too
695         {
696                 //draw the text
697                 color *= 0.5 + pps_ratio * (1 - 0.5); // half saturated color at min, full saturated at max
698                 if (layout == 2) // average pps
699                         drawstring_aspect(myPos + eX * mySize.y, ftos_decimals(stat, 2), eX * (2/3) * mySize.x + eY * mySize.y, color, panel_fg_alpha, DRAWFLAG_NORMAL);
700                 else // percentage of average pps
701                         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);
702         }
703
704         //draw the icon
705         drawpic_aspect_skin(myPos, pic, '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
706         if (stat > 0)
707         {
708                 drawsetcliparea(myPos.x, myPos.y + mySize.y * (1 - pps_ratio), mySize.y, mySize.y * pps_ratio);
709                 drawpic_aspect_skin(myPos, strcat(pic, "-highlighted"), '1 1 0' * mySize.y, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
710                 drawresetcliparea();
711         }
712 }
713
714 void HUD_Mod_Dom(vector myPos, vector mySize)
715 {
716         mod_active = 1; // required in each mod function that always shows something
717
718         int layout = autocvar_hud_panel_modicons_dom_layout;
719         int rows, columns;
720         float aspect_ratio;
721         aspect_ratio = (layout) ? 3 : 1;
722         rows = HUD_GetRowCount(team_count, mySize, aspect_ratio);
723         columns = ceil(team_count/rows);
724
725         int i;
726         float row = 0, column = 0;
727         vector pos, itemSize;
728         itemSize = eX * mySize.x*(1/columns) + eY * mySize.y*(1/rows);
729         for(i=0; i<team_count; ++i)
730         {
731                 pos = myPos + eX * column * itemSize.x + eY * row * itemSize.y;
732
733                 DrawDomItem(pos, itemSize, aspect_ratio, layout, i);
734
735                 ++row;
736                 if(row >= rows)
737                 {
738                         row = 0;
739                         ++column;
740                 }
741         }
742 }
743
744 void HUD_ModIcons_SetFunc()
745 {
746         switch(gametype)
747         {
748                 case MAPINFO_TYPE_KEYHUNT:              HUD_ModIcons_GameType = HUD_Mod_KH; break;
749                 case MAPINFO_TYPE_CTF:                  HUD_ModIcons_GameType = HUD_Mod_CTF; break;
750                 case MAPINFO_TYPE_NEXBALL:              HUD_ModIcons_GameType = HUD_Mod_NexBall; break;
751                 case MAPINFO_TYPE_CTS:
752                 case MAPINFO_TYPE_RACE:         HUD_ModIcons_GameType = HUD_Mod_Race; break;
753                 case MAPINFO_TYPE_CA:
754                 case MAPINFO_TYPE_FREEZETAG:    HUD_ModIcons_GameType = HUD_Mod_CA; break;
755                 case MAPINFO_TYPE_DOMINATION:   HUD_ModIcons_GameType = HUD_Mod_Dom; break;
756                 case MAPINFO_TYPE_KEEPAWAY:     HUD_ModIcons_GameType = HUD_Mod_Keepaway; break;
757         }
758 }
759
760 int mod_prev; // previous state of mod_active to check for a change
761 float mod_alpha;
762 float mod_change; // "time" when mod_active changed
763
764 void HUD_ModIcons()
765 {
766         if(!autocvar__hud_configure)
767         {
768                 if(!autocvar_hud_panel_modicons) return;
769                 if(!HUD_ModIcons_GameType) return;
770         }
771
772         HUD_Panel_UpdateCvars();
773
774         draw_beginBoldFont();
775
776         if(mod_active != mod_prev) {
777                 mod_change = time;
778                 mod_prev = mod_active;
779         }
780
781         if(mod_active || autocvar__hud_configure)
782                 mod_alpha = bound(0, (time - mod_change) * 2, 1);
783         else
784                 mod_alpha = bound(0, 1 - (time - mod_change) * 2, 1);
785
786         if (autocvar_hud_panel_modicons_dynamichud)
787                 HUD_Scale_Enable();
788         else
789                 HUD_Scale_Disable();
790         if(mod_alpha)
791                 HUD_Panel_DrawBg(mod_alpha);
792
793         if(panel_bg_padding)
794         {
795                 panel_pos += '1 1 0' * panel_bg_padding;
796                 panel_size -= '2 2 0' * panel_bg_padding;
797         }
798
799         if(autocvar__hud_configure)
800                 HUD_Mod_CTF(panel_pos, panel_size);
801         else
802                 HUD_ModIcons_GameType(panel_pos, panel_size);
803
804         draw_endBoldFont();
805 }