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