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