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