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