]> icculus.org git repositories - divverent/nexuiz.git/blob - data/qcsrc/client/sbar.qc
scoreboard fixes by terencehill
[divverent/nexuiz.git] / data / qcsrc / client / sbar.qc
1 void drawstringright(vector, string, vector, vector, float, float);
2 void drawstringcenter(vector, string, vector, vector, float, float);
3 void Sbar_DrawAccuracyStats();
4 void Sbar_DrawAccuracyStats_Description_Splash(vector);
5 void Sbar_DrawAccuracyStats_Description_Hitscan(vector);
6 float weapon_hits[WEP_COUNT];
7 float weapon_fired[WEP_COUNT];
8 float weapon_number;
9
10 float last_weapon;
11 float weapontime;
12
13 float sbar_alpha_fg;
14 float sbar_alpha_bg;
15 float sbar_color_bg_team;
16 float sbar_border_thickness;
17 float sbar_scoreboard_alpha_bg;
18 float sbar_scoreboard_highlight;
19 float sbar_hudselector;
20 float sbar_hud_accuracy;
21
22 float ps_primary, ps_secondary;
23 float ts_primary, ts_secondary;
24
25 vector sbar, color;
26 vector element_offset = '0 6 0'; // global item offset from the bottom edge
27 float SCOREBOARD_OFFSET = 50;
28
29 void CSQC_kh_hud();
30 void CSQC_ctf_hud();
31 void CSQC_nb_hud();
32 void MapVote_Draw();
33 void Sbar_FinaleOverlay()
34 {
35         /*vector pos;
36         pos_x = (vid_conwidth - 1)/2;
37         pos_y = 16;
38         pos_z = 0;*/
39
40         //drawpic(pos, "gfx/finale", '0 0 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
41
42         //drawstring(pos, "END", sbar_fontsize, '1 1 1', 1, DRAWFLAG_NORMAL);
43         MapVote_Draw();
44 }
45
46 float weaponspace[10];
47 float weapon_first, weapon_last;
48 void Sbar_DrawWeapon_Clear()
49 {
50         float idx;
51         weapon_first = -2;
52         weapon_last = -1;
53         for(idx = 0; idx < 10; ++idx)
54                 weaponspace[idx] = 0;
55         for(idx = 0; idx <= 23; ++idx)
56         {
57                 if(weaponimpulse[idx] >= 0)
58                 {
59                         if(weapon_first < 0)
60                                 weapon_first = idx;
61                         weapon_last = idx;
62                 }
63         }
64 }
65 void Sbar_DrawWeapon(float nr, float fade, float active, float wc)
66 {
67         vector pos, vsize, fill_colour;
68         float value, idx, imp, sp, weapon_hit, weapon_damage, weapon_stats;
69
70         imp = weaponimpulse[nr];
71         weapon_hit = weapon_hits[wc];
72         weapon_damage = weapon_fired[wc];
73         if(imp == 0)
74                 idx = 9;
75         else
76                 idx = imp - 1;
77
78         value = (active) ? 1 : 0.6;
79         color_x = color_y = color_z = value;
80
81         // width = 300, height = 100
82         const float w_width = 25, w_height = 12, w_space = 2, font_size = 8;
83
84         sp = weaponspace[idx] + 1;
85         weaponspace[idx] = sp;
86
87         pos_x = (vid_conwidth + 10 - w_width * 9) * 0.5 + w_width * idx;
88         pos_y = (vid_conheight - w_height * sp) - 38; // move 38 pixels up
89         pos_z = 0;
90         vsize_x = w_width;
91         vsize_y = w_height;
92         vsize_z = 0;
93         if (active)
94                 drawpic(pos, "gfx/hud/sb_ammobg", vsize, color, value * fade * sbar_alpha_fg, DRAWFLAG_NORMAL);
95         drawpic(pos, strcat("gfx/hud/inv_weapon", ftos(nr)), vsize, color, value * fade * sbar_alpha_fg, DRAWFLAG_NORMAL);
96         pos_x += w_space;
97         pos_y += w_space;
98         vsize_x = font_size;
99         vsize_y = font_size;
100         vsize_z = 0;
101         drawstring(pos, ftos(imp), vsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
102
103         // draw the weapon accuracy on the HUD
104         if(sbar_hud_accuracy)
105         {
106                 weapon_stats = rint(100*weapon_hit/weapon_damage);
107                 fill_colour_x = 1 - 0.015 * weapon_stats;
108                 fill_colour_y = 1 - 0.015 * (100 - weapon_stats);
109                 drawstringright(pos + '22 0 0', strcat(ftos(weapon_stats),"%"), '6 6 0', fill_colour, sbar_alpha_fg, 0);
110         }
111 }
112
113 void Sbar_DrawXNum (vector pos, float num, float digits, float lettersize, vector rgb, float highlighted, float stroke, float alpha, float dflags)
114 {
115         float l, i;
116         string str, tmp, l_length;
117         float minus;
118         vector vsize, num_color;
119
120         vsize_x = vsize_y = lettersize;
121         vsize_z = 0;
122
123         if(num < 0)
124         {
125                 minus = true;
126                 num = -num;
127                 pos_x -= lettersize;
128         } else
129                 minus = false;
130
131         if(digits < 0)
132         {
133                 tmp = ftos(num);
134                 digits = -digits;
135                 str = strcat(substring("0000000000", 0, digits - strlen(tmp)), tmp);
136         } else
137                 str = ftos(num);
138
139         l = strlen(str);
140         l_length = ftos(l);
141
142         if(l > digits)
143         {
144                 str = substring(str, l-digits, 999);
145                 l = strlen(str);
146         } else if(l < digits)
147                 pos_x += (digits-l) * lettersize;
148
149         if (highlighted == 1) {
150                 vector hl_size;
151                 hl_size_x = vsize_x * l + vsize_x * 0.2;
152                 hl_size_y = vsize_y * 1.1;
153                 hl_size_z = 0;
154                 if(minus)
155                         hl_size_x = hl_size_x + vsize_x;
156
157                 vector hl_pos;
158                 hl_pos_x = pos_x - lettersize/10;
159                 hl_pos_y = pos_y - lettersize/20;
160                 hl_pos_z = 0;
161
162                 drawpic(hl_pos, strcat("gfx/hud/sb_highlight_", l_length), hl_size, '1 1 1', alpha, dflags);
163         }
164
165         if (stroke == 1)
166                 num_color = '1 1 1';
167         else
168                 num_color = rgb;
169
170         if(minus)
171         {
172                 if (stroke == 1)
173                         drawpic(pos, "gfx/hud/num_minus_stroke", vsize, rgb, alpha, dflags);
174                 drawpic(pos, "gfx/hud/num_minus", vsize, num_color, alpha, dflags);
175                 pos_x += lettersize;
176         }
177
178         for(i = 0; i < l; ++i)
179         {
180                 tmp = substring(str, i, 1);
181                 if (stroke == 1)
182                         drawpic(pos, strcat("gfx/hud/num_", tmp, "_stroke"), vsize, rgb, alpha, dflags);
183                 drawpic(pos, strcat("gfx/hud/num_", tmp), vsize, num_color, alpha, dflags);
184                 pos_x += lettersize;
185         }
186 }
187
188 void Sbar_DrawXNum_Colored (vector pos, float x, float lettersize, float alpha)
189 {
190         if(x > 200) {
191                 color_x = 0;
192                 color_y = 1;
193                 color_z = 0;
194         }
195         else if(x > 150) {
196                 color_x = 0.4 - (x-150)*0.02 * 0.4; //red value between 0.4 -> 0
197                 color_y = 0.9 + (x-150)*0.02 * 0.1; // green value between 0.9 -> 1
198                 color_z = 0;
199         }
200         else if(x > 100) {
201                 color_x = 1 - (x-100)*0.02 * 0.6; //red value between 1 -> 0.4
202                 color_y = 1 - (x-100)*0.02 * 0.1; // green value between 1 -> 0.9
203                 color_z = 1 - (x-100)*0.02; // blue value between 1 -> 0
204         }
205         else if(x > 50) {
206                 color_x = 1;
207                 color_y = 1;
208                 color_z = 0.2 + (x-50)*0.02 * 0.8; // blue value between 0.2 -> 1
209         }
210         else if(x > 20) {
211                 color_x = 1;
212                 color_y = (x-20)*90/27/100; // green value between 0 -> 1
213                 color_z = (x-20)*90/27/100 * 0.2; // blue value between 0 -> 0.2
214         }
215         else {
216                 color_x = 1;
217                 color_y = 0;
218                 color_z = 0;
219         }
220         Sbar_DrawXNum(pos, x, 3, lettersize, color, 0, 0, alpha, DRAWFLAG_NORMAL);
221 }
222
223 void Cmd_Sbar_SetFields(float argc);
224 void Sbar_InitScores()
225 {
226         float i, f;
227
228         ps_primary = ps_secondary = ts_primary = ts_secondary = -1;
229         for(i = 0; i < MAX_SCORE; ++i)
230         {
231                 f = (scores_flags[i] & SFL_SORT_PRIO_MASK);
232                 if(f == SFL_SORT_PRIO_PRIMARY)
233                         ps_primary = i;
234                 if(f == SFL_SORT_PRIO_SECONDARY)
235                         ps_secondary = i;
236         }
237         if(ps_secondary == -1)
238                 ps_secondary = ps_primary;
239
240         for(i = 0; i < MAX_TEAMSCORE; ++i)
241         {
242                 f = (teamscores_flags[i] & SFL_SORT_PRIO_MASK);
243                 if(f == SFL_SORT_PRIO_PRIMARY)
244                         ts_primary = i;
245                 if(f == SFL_SORT_PRIO_SECONDARY)
246                         ts_secondary = i;
247         }
248         if(ts_secondary == -1)
249                 ts_secondary = ts_primary;
250
251         Cmd_Sbar_SetFields(0);
252 }
253
254 void Sbar_UpdatePlayerPos(entity pl);
255 float SetTeam(entity pl, float Team);
256 //float lastpnum;
257 void Sbar_UpdatePlayerTeams()
258 {
259         float Team;
260         entity pl, tmp;
261         float num;
262
263         num = 0;
264         for(pl = players.sort_next; pl; pl = pl.sort_next)
265         {
266                 num += 1;
267                 Team = GetPlayerColor(pl.sv_entnum);
268                 if(SetTeam(pl, Team))
269                 {
270                         tmp = pl.sort_prev;
271                         Sbar_UpdatePlayerPos(pl);
272                         if(tmp)
273                                 pl = tmp;
274                         else
275                                 pl = players.sort_next;
276                 }
277         }
278         /*
279         if(num != lastpnum)
280                 print(strcat("PNUM: ", ftos(num), "\n"));
281         lastpnum = num;
282         */
283 }
284
285 float Sbar_ComparePlayerScores(entity left, entity right)
286 {
287         float vl, vr;
288         vl = GetPlayerColor(left.sv_entnum);
289         vr = GetPlayerColor(right.sv_entnum);
290
291         if(!left.gotscores)
292                 vl = COLOR_SPECTATOR;
293         if(!right.gotscores)
294                 vr = COLOR_SPECTATOR;
295
296         if(vl > vr)
297                 return true;
298         if(vl < vr)
299                 return false;
300
301         if(vl == COLOR_SPECTATOR)
302         {
303                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
304                 // no other sorting
305                 if(!left.gotscores && right.gotscores)
306                         return true;
307                 return false;
308         }
309
310         vl = left.scores[ps_primary];
311         vr = right.scores[ps_primary];
312         if(scores_flags[ps_primary] & SFL_ZERO_IS_WORST)
313         {
314                 if(vl == 0 && vr != 0)
315                         return 1;
316                 if(vl != 0 && vr == 0)
317                         return 0;
318         }
319         if(vl > vr)
320                 return IS_INCREASING(scores_flags[ps_primary]);
321         if(vl < vr)
322                 return IS_DECREASING(scores_flags[ps_primary]);
323
324         vl = left.scores[ps_secondary];
325         vr = right.scores[ps_secondary];
326         if(scores_flags[ps_secondary] & SFL_ZERO_IS_WORST)
327         {
328                 if(vl == 0 && vr != 0)
329                         return 1;
330                 if(vl != 0 && vr == 0)
331                         return 0;
332         }
333         if(vl > vr)
334                 return IS_INCREASING(scores_flags[ps_secondary]);
335         if(vl < vr)
336                 return IS_DECREASING(scores_flags[ps_secondary]);
337
338         return false;
339 }
340
341 void Sbar_UpdatePlayerPos(entity player)
342 {
343         for(other = player.sort_next; other && Sbar_ComparePlayerScores(player, other); other = player.sort_next)
344         {
345                 SORT_SWAP(player, other);
346         }
347         for(other = player.sort_prev; other != players && Sbar_ComparePlayerScores(other, player); other = player.sort_prev)
348         {
349                 SORT_SWAP(other, player);
350         }
351 }
352
353 float Sbar_CompareTeamScores(entity left, entity right)
354 {
355         float vl, vr;
356
357         if(left.team == COLOR_SPECTATOR)
358                 return 1;
359         if(right.team == COLOR_SPECTATOR)
360                 return 0;
361
362         vl = left.teamscores[ts_primary];
363         vr = right.teamscores[ts_primary];
364         if(vl > vr)
365                 return IS_INCREASING(teamscores_flags[ts_primary]);
366         if(vl < vr)
367                 return IS_DECREASING(teamscores_flags[ts_primary]);
368
369         vl = left.teamscores[ts_secondary];
370         vr = right.teamscores[ts_secondary];
371         if(vl > vr)
372                 return IS_INCREASING(teamscores_flags[ts_secondary]);
373         if(vl < vr)
374                 return IS_DECREASING(teamscores_flags[ts_secondary]);
375
376         return false;
377 }
378
379 void Sbar_UpdateTeamPos(entity Team)
380 {
381         for(other = Team.sort_next; other && Sbar_CompareTeamScores(Team, other); other = Team.sort_next)
382         {
383                 SORT_SWAP(Team, other);
384         }
385         for(other = Team.sort_prev; other != teams && Sbar_CompareTeamScores(other, Team); other = Team.sort_prev)
386         {
387                 SORT_SWAP(other, Team);
388         }
389 }
390
391 void Cmd_Sbar_Help(float argc)
392 {
393         print("You can modify the scoreboard using the\n");
394         print("^3|---------------------------------------------------------------|\n");
395         print("^1 TO BE DONE\n");
396         print("Usage:\n");
397         print("^2sbar_columns_set default\n");
398         print("^2sbar_columns_set ^7filed1 field2 ...\n");
399         print("The following field names are recognized (case INsensitive):\n");
400         print("You can use a ^3|^7 to start the right-aligned fields.\n");
401
402         print("^3name^7 or ^3nick^7             Name of a player\n");
403         print("^3ping^7                     Ping time\n\n");
404         print("^3kd^7 or ^3kdr^7 or ^3kdratio^7 or ^3k/d\n");
405         print("                         The kill-death ratio\n");
406
407         print("Before a field you can put a + or - sign, then a comma separated list\n");
408         print("of game types, then a slash, to make the field show up only in these\n");
409         print("or in all but these game types.\n");
410
411         print("The special game type names 'teams' and 'noteams' can be used to\n");
412         print("include/exclude ALL teams/noteams game modes.\n");
413
414         local float i;
415         print("Additional columns:\n");
416         for(i = 0; i < MAX_SCORE; ++i)
417         {
418                 if(scores_label[i] != "")
419                         print(strcat(scores_label[i], "\n"));
420         }
421 }
422
423 string Sbar_DefaultColumnLayout()
424 {
425         return strcat( // fteqcc sucks
426                 "ping pl name | ",
427                 "-teams,race,lms/kills -teams,lms/deaths -teams,lms,race/suicides -race,dm,tdm/frags ", // tdm already has this in "score"
428                 "+ctf/caps +ctf/pickups +ctf/fckills +ctf/returns ",
429                 "+lms/lives +lms/rank ",
430                 "+kh/caps +kh/pushes +kh/destroyed ",
431                 "?+race/laps ?+race/time ?+race/fastest ",
432                 "+as/objectives +nexball/faults +nexball/goals ",
433                 "-lms,race,nexball/score");
434 }
435
436 void Cmd_Sbar_SetFields(float argc)
437 {
438         float i, j, slash;
439         string str, pattern;
440         float digit;
441         float have_name, have_primary, have_secondary, have_separator;
442         float missing;
443
444         // TODO: re enable with gametype dependant cvars?
445         if(argc < 2) // no arguments provided
446                 argc = tokenizebyseparator(strcat("x ", cvar_string("sbar_columns")), " ");
447
448         if(argc < 2)
449                 argc = tokenizebyseparator(strcat("x ", Sbar_DefaultColumnLayout()), " ");
450
451         if(argc == 2)
452         {
453                 if(argv(1) == "default")
454                         argc = tokenizebyseparator(strcat("x ", Sbar_DefaultColumnLayout()), " ");
455                 else if(argv(1) == "all")
456                 {
457                         string s;
458                         s = "ping pl color name |";
459                         for(i = 0; i < MAX_SCORE; ++i)
460                         {
461                                 if(i != ps_primary)
462                                 if(i != ps_secondary)
463                                 if(scores_label[i] != "")
464                                         s = strcat(s, " ", scores_label[i]);
465                         }
466                         if(ps_secondary != ps_primary)
467                                 s = strcat(s, " ", scores_label[ps_secondary]);
468                         s = strcat(s, " ", scores_label[ps_primary]);
469                         argc = tokenizebyseparator(strcat("x ", s), " ");
470                 }
471         }
472
473
474         sbar_num_fields = 0;
475
476         drawfont = sbar_font;
477         digit = stringwidth("0123456789", FALSE) / 10;
478
479         for(i = 0; i < argc - 1; ++i)
480         {
481                 float nocomplain;
482                 str = argv(i+1);
483
484                 nocomplain = FALSE;
485                 if(substring(str, 0, 1) == "?")
486                 {
487                         nocomplain = TRUE;
488                         str = substring(str, 1, strlen(str) - 1);
489                 }
490
491                 slash = strstrofs(str, "/", 0);
492                 if(slash >= 0)
493                 {
494                         pattern = substring(str, 0, slash);
495                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
496
497                         if not(isGametypeInFilter(gametype, teamplay, pattern))
498                                 continue;
499                 }
500
501                 strunzone(sbar_title[sbar_num_fields]);
502                 sbar_title[sbar_num_fields] = strzone(str);
503                 sbar_size[sbar_num_fields] = stringwidth(str, FALSE);
504                 str = strtolower(str);
505
506                 if(str == "ping") {
507                         sbar_field[sbar_num_fields] = SP_PING;
508                 } else if(str == "pl") {
509                         sbar_field[sbar_num_fields] = SP_PL;
510                 } else if(str == "kd" || str == "kdr" || str == "kdratio" || str == "k/d") {
511                         sbar_field[sbar_num_fields] = SP_KDRATIO;
512                 } else if(str == "name" || str == "nick") {
513                         sbar_field[sbar_num_fields] = SP_NAME;
514                         have_name = 1;
515                 } else if(str == "|") {
516                         sbar_field[sbar_num_fields] = SP_SEPARATOR;
517                         have_separator = 1;
518                 } else {
519                         for(j = 0; j < MAX_SCORE; ++j)
520                                 if(str == strtolower(scores_label[j]))
521                                         goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
522 :notfound
523                         if(str == "frags")
524                         {
525                                 j = SP_FRAGS;
526                         }
527                         else
528                         {
529                                 if not(nocomplain)
530                                         print(strcat("^1Error:^7 Unknown score field: '", str, "'\n"));
531                                 continue;
532                         }
533 :found
534                         sbar_field[sbar_num_fields] = j;
535                         if(j == ps_primary)
536                                 have_primary = 1;
537                         if(j == ps_secondary)
538                                 have_secondary = 1;
539                 }
540                 ++sbar_num_fields;
541                 if(sbar_num_fields >= MAX_SBAR_FIELDS)
542                         break;
543         }
544
545         if(scores_flags[ps_primary] & SFL_ALLOW_HIDE)
546                 have_primary = 1;
547         if(scores_flags[ps_secondary] & SFL_ALLOW_HIDE)
548                 have_secondary = 1;
549         if(ps_primary == ps_secondary)
550                 have_secondary = 1;
551         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
552
553         if(sbar_num_fields+missing < MAX_SBAR_FIELDS)
554         {
555                 if(!have_name)
556                 {
557                         strunzone(sbar_title[sbar_num_fields]);
558                         for(i = sbar_num_fields; i > 0; --i)
559                         {
560                                 sbar_title[i] = sbar_title[i-1];
561                                 sbar_size[i] = sbar_size[i-1];
562                                 sbar_field[i] = sbar_field[i-1];
563                         }
564                         sbar_title[0] = strzone("name");
565                         sbar_field[0] = SP_NAME;
566                         ++sbar_num_fields;
567                         print("fixed missing field 'name'\n");
568
569                         if(!have_separator)
570                         {
571                                 strunzone(sbar_title[sbar_num_fields]);
572                                 for(i = sbar_num_fields; i > 1; --i)
573                                 {
574                                         sbar_title[i] = sbar_title[i-1];
575                                         sbar_size[i] = sbar_size[i-1];
576                                         sbar_field[i] = sbar_field[i-1];
577                                 }
578                                 sbar_title[1] = strzone("|");
579                                 sbar_field[1] = SP_SEPARATOR;
580                                 sbar_size[1] = stringwidth("|", FALSE);
581                                 ++sbar_num_fields;
582                                 print("fixed missing field '|'\n");
583                         }
584                 }
585                 else if(!have_separator)
586                 {
587                         strunzone(sbar_title[sbar_num_fields]);
588                         sbar_title[sbar_num_fields] = strzone("|");
589                         sbar_size[sbar_num_fields] = stringwidth("|", FALSE);
590                         sbar_field[sbar_num_fields] = SP_SEPARATOR;
591                         ++sbar_num_fields;
592                         print("fixed missing field '|'\n");
593                 }
594                 if(!have_secondary)
595                 {
596                         strunzone(sbar_title[sbar_num_fields]);
597                         sbar_title[sbar_num_fields] = strzone(scores_label[ps_secondary]);
598                         sbar_size[sbar_num_fields] = stringwidth(sbar_title[sbar_num_fields], FALSE);
599                         sbar_field[sbar_num_fields] = ps_secondary;
600                         ++sbar_num_fields;
601                         print("fixed missing field '", scores_label[ps_secondary], "'\n");
602                 }
603                 if(!have_primary)
604                 {
605                         strunzone(sbar_title[sbar_num_fields]);
606                         sbar_title[sbar_num_fields] = strzone(scores_label[ps_primary]);
607                         sbar_size[sbar_num_fields] = stringwidth(sbar_title[sbar_num_fields], FALSE);
608                         sbar_field[sbar_num_fields] = ps_primary;
609                         ++sbar_num_fields;
610                         print("fixed missing field '", scores_label[ps_primary], "'\n");
611                 }
612         }
613
614         sbar_field[sbar_num_fields] = SP_END;
615 }
616
617 // MOVEUP::
618 vector sbar_field_rgb;
619 string sbar_field_icon0;
620 string sbar_field_icon1;
621 string sbar_field_icon2;
622 vector sbar_field_icon0_rgb;
623 vector sbar_field_icon1_rgb;
624 vector sbar_field_icon2_rgb;
625 float sbar_field_icon0_alpha;
626 float sbar_field_icon1_alpha;
627 float sbar_field_icon2_alpha;
628 string Sbar_GetField(entity pl, float field)
629 {
630         float tmp, num, denom, f;
631         string str;
632         sbar_field_rgb = '1 1 1';
633         sbar_field_icon0 = "";
634         sbar_field_icon1 = "";
635         sbar_field_icon2 = "";
636         sbar_field_icon0_rgb = '1 1 1';
637         sbar_field_icon1_rgb = '1 1 1';
638         sbar_field_icon2_rgb = '1 1 1';
639         sbar_field_icon0_alpha = 1;
640         sbar_field_icon1_alpha = 1;
641         sbar_field_icon2_alpha = 1;
642         switch(field)
643         {
644                 case SP_PING:
645                         if not(pl.gotscores)
646                                 return "\x8D\x8D\x8D"; // >>> sign
647                         str = getplayerkey(pl.sv_entnum, "ping");
648                         if(str == "0")
649                                 return "N/A";
650                         tmp = max(0, min(220, stof(str)-80)) / 220;
651                         sbar_field_rgb = '1 1 1' - '0 1 1'*tmp;
652                         return str;
653
654                 case SP_PL:
655                         if not(pl.gotscores)
656                                 return "N/A";
657                         str = getplayerkey(pl.sv_entnum, "pl");
658                         if(str == "0")
659                                 return "";
660                         tmp = bound(0, stof(str), 20) / 20; // 20% is REALLY BAD pl
661                         sbar_field_rgb = '1 0.5 0.5' - '0 0.5 0.5'*tmp;
662                         return str;
663
664                 case SP_NAME:
665                         if(ready_waiting && pl.ready)
666                         {
667                                 sbar_field_icon0 = "gfx/sb_player_ready";
668                         }
669                         else if(!teamplay)
670                         {
671                                 f = stof(getplayerkey(pl.sv_entnum, "colors"));
672                                 {
673                                         sbar_field_icon0 = "gfx/sb_playercolor_base";
674                                         sbar_field_icon1 = "gfx/sb_playercolor_shirt";
675                                         sbar_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
676                                         sbar_field_icon2 = "gfx/sb_playercolor_pants";
677                                         sbar_field_icon2_rgb = colormapPaletteColor(mod(f, 16), 1);
678                                 }
679                         }
680                         return GetPlayerName(pl.sv_entnum);
681
682                 case SP_FRAGS:
683                         f = pl.(scores[SP_KILLS]);
684                         f -= pl.(scores[SP_SUICIDES]);
685                         return ftos(f);
686
687                 case SP_KDRATIO:
688                         num = pl.(scores[SP_KILLS]);
689                         denom = pl.(scores[SP_DEATHS]);
690
691                         if(denom == 0) {
692                                 sbar_field_rgb = '0 1 0';
693                                 str = ftos(num);
694                         } else if(num <= 0) {
695                                 sbar_field_rgb = '1 0 0';
696                                 str = ftos(num/denom);
697                         } else
698                                 str = ftos(num/denom);
699
700                         tmp = strstrofs(str, ".", 0);
701                         if(tmp > 0)
702                                 str = substring(str, 0, tmp+2);
703                         return str;
704
705                 default:
706                         tmp = pl.(scores[field]);
707                         f = scores_flags[field];
708                         if(field == ps_primary)
709                                 sbar_field_rgb = '1 1 0';
710                         else if(field == ps_secondary)
711                                 sbar_field_rgb = '0 1 1';
712                         else
713                                 sbar_field_rgb = '1 1 1';
714                         return ScoreString(f, tmp);
715         }
716         //return "error";
717 }
718
719 float xmin, xmax, ymin, ymax, sbwidth;
720 float sbar_fixscoreboardcolumnwidth_len;
721 float sbar_fixscoreboardcolumnwidth_iconlen;
722 float sbar_fixscoreboardcolumnwidth_marginlen;
723
724 float stringwidth_colors(string s)
725 {
726         return stringwidth(s, TRUE);
727 }
728
729 float stringwidth_nocolors(string s)
730 {
731         return stringwidth(s, FALSE);
732 }
733
734 string Sbar_FixScoreboardColumnWidth(float i, string str)
735 {
736         float field, f;
737         vector sz;
738         field = sbar_field[i];
739
740         sbar_fixscoreboardcolumnwidth_iconlen = 0;
741
742         if(sbar_field_icon0 != "")
743         {
744                 sz = drawgetimagesize(sbar_field_icon0);
745                 f = sz_x / sz_y;
746                 if(sbar_fixscoreboardcolumnwidth_iconlen < f)
747                         sbar_fixscoreboardcolumnwidth_iconlen = f;
748         }
749
750         if(sbar_field_icon1 != "")
751         {
752                 sz = drawgetimagesize(sbar_field_icon1);
753                 f = sz_x / sz_y;
754                 if(sbar_fixscoreboardcolumnwidth_iconlen < f)
755                         sbar_fixscoreboardcolumnwidth_iconlen = f;
756         }
757
758         if(sbar_field_icon2 != "")
759         {
760                 sz = drawgetimagesize(sbar_field_icon2);
761                 f = sz_x / sz_y;
762                 if(sbar_fixscoreboardcolumnwidth_iconlen < f)
763                         sbar_fixscoreboardcolumnwidth_iconlen = f;
764         }
765
766         sbar_fixscoreboardcolumnwidth_iconlen *= sbar_fontsize_y / sbar_fontsize_x; // fix icon aspect
767
768         if(sbar_fixscoreboardcolumnwidth_iconlen != 0)
769                 sbar_fixscoreboardcolumnwidth_marginlen = stringwidth(" ", FALSE);
770         else
771                 sbar_fixscoreboardcolumnwidth_marginlen = 0;
772
773         if(field == SP_NAME) // name gets all remaining space
774         {
775                 float size, j;
776                 size = sbwidth / sbar_fontsize_x;
777                 for(j = 0; j < sbar_num_fields; ++j)
778                         if(j != i)
779                                 if (sbar_field[i] != SP_SEPARATOR)
780                                         size -= sbar_size[j] + 1;
781                 size += 1;
782                 sbar_size[i] = size;
783                 
784                 if (sbar_fixscoreboardcolumnwidth_iconlen != 0)
785                         size -= sbar_fixscoreboardcolumnwidth_marginlen + sbar_fixscoreboardcolumnwidth_iconlen;
786                 str = textShortenToWidth(str, size, stringwidth_colors);
787                 sbar_fixscoreboardcolumnwidth_len = stringwidth(str, TRUE);
788         }
789         else
790                 sbar_fixscoreboardcolumnwidth_len = stringwidth(str, FALSE);
791         
792         f = sbar_fixscoreboardcolumnwidth_len + sbar_fixscoreboardcolumnwidth_marginlen + sbar_fixscoreboardcolumnwidth_iconlen;
793         if(sbar_size[i] < f)
794                 sbar_size[i] = f;
795
796         return str;
797 }
798
799 void Sbar_PrintScoreboardItem(vector pos, entity pl, float is_self, float pl_number)
800 {
801         vector tmp;
802         string str;
803         float i, field;
804         float is_spec;
805         is_spec = (GetPlayerColor(pl.sv_entnum) == COLOR_SPECTATOR);
806
807         // Layout:
808         tmp_x = sbwidth;
809         tmp_y = sbar_fontsize_y * 1.25;
810         tmp_z = 0;
811
812         // alternated rows highlighting
813         if (is_self)
814                 drawfill(pos - '1 1 0', tmp + '2 0 0', '1 1 1', 0.25, DRAWFLAG_NORMAL);
815         else
816         {
817                 if (sbar_scoreboard_highlight)
818                         if(!mod(pl_number,2))
819                                 drawfill(pos - '1 1 0', tmp + '2 0 0', '1 1 1', 0.09, DRAWFLAG_NORMAL);
820         }
821
822         tmp_y = 0;
823
824         for(i = 0; i < sbar_num_fields; ++i)
825         {
826                 field = sbar_field[i];
827                 if(field == SP_SEPARATOR)
828                         break;
829
830                 if(is_spec && field != SP_NAME && field != SP_PING) {
831                         pos_x += sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
832                         continue;
833                 }
834                 str = Sbar_GetField(pl, field);
835                 str = Sbar_FixScoreboardColumnWidth(i, str);
836
837                 pos_x += sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
838
839                 if(field == SP_NAME) {
840                         tmp_x = sbar_fontsize_x*(sbar_size[i] - sbar_fixscoreboardcolumnwidth_iconlen - sbar_fixscoreboardcolumnwidth_marginlen) + sbar_fontsize_x;
841                         drawcolorcodedstring(pos - tmp, str, sbar_fontsize, 1, DRAWFLAG_NORMAL);
842                 } else {
843                         tmp_x = sbar_fixscoreboardcolumnwidth_len*sbar_fontsize_x + sbar_fontsize_x;
844                         drawstring(pos - tmp, str, sbar_fontsize, sbar_field_rgb, 1, DRAWFLAG_NORMAL);
845                 }
846
847                 tmp_x = sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
848                 if(sbar_field_icon0 != "")
849                         drawpic(pos - tmp, sbar_field_icon0, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon1_rgb, sbar_field_icon0_alpha, DRAWFLAG_NORMAL);
850                 if(sbar_field_icon1 != "")
851                         drawpic(pos - tmp, sbar_field_icon1, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon1_rgb, sbar_field_icon1_alpha, DRAWFLAG_NORMAL);
852                 if(sbar_field_icon2 != "")
853                         drawpic(pos - tmp, sbar_field_icon2, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon2_rgb, sbar_field_icon2_alpha, DRAWFLAG_NORMAL);
854         }
855
856         if(sbar_field[i] == SP_SEPARATOR)
857         {
858                 pos_x = xmax;
859                 for(i = sbar_num_fields-1; i > 0; --i)
860                 {
861                         field = sbar_field[i];
862                         if(field == SP_SEPARATOR)
863                                 break;
864
865                         if(is_spec && field != SP_NAME && field != SP_PING) {
866                                 pos_x -= sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
867                                 continue;
868                         }
869
870                         str = Sbar_GetField(pl, field);
871                         str = Sbar_FixScoreboardColumnWidth(i, str);
872
873                         if(field == SP_NAME) {
874                                 tmp_x = sbar_fontsize_x*sbar_fixscoreboardcolumnwidth_len; // left or right aligned? let's put it right...
875                                 drawcolorcodedstring(pos - tmp, str, sbar_fontsize, 1, DRAWFLAG_NORMAL);
876                         } else {
877                                 tmp_x = sbar_fontsize_x*sbar_fixscoreboardcolumnwidth_len;
878                                 drawstring(pos - tmp, str, sbar_fontsize, sbar_field_rgb, 1, DRAWFLAG_NORMAL);
879                         }
880
881                         tmp_x = sbar_fontsize_x*sbar_size[i];
882                         if(sbar_field_icon0 != "")
883                                 drawpic(pos - tmp, sbar_field_icon0, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon1_rgb, sbar_field_icon0_alpha, DRAWFLAG_NORMAL);
884                         if(sbar_field_icon1 != "")
885                                 drawpic(pos - tmp, sbar_field_icon1, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon1_rgb, sbar_field_icon1_alpha, DRAWFLAG_NORMAL);
886                         if(sbar_field_icon2 != "")
887                                 drawpic(pos - tmp, sbar_field_icon2, '0 1 0' * sbar_fontsize_y + '1 0 0' * sbar_fontsize_x * sbar_fixscoreboardcolumnwidth_iconlen, sbar_field_icon2_rgb, sbar_field_icon2_alpha, DRAWFLAG_NORMAL);
888
889                         pos_x -= sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
890                 }
891         }
892 }
893
894 /*
895  * Sbar_Scoreboard_MakeTable
896  *
897  * Makes a table for a team (for all playing players in DM) and fills it
898  */
899
900 vector Sbar_Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
901 {
902         float body_table_height, i;
903         vector tmp, column_dim;
904         entity pl;
905
906         body_table_height = 1.25 * sbar_fontsize_y * max(1, tm.team_size); // no player? show 1 empty line
907
908         pos -= '1 1 0';
909
910         tmp_x = sbwidth + 2;
911         tmp_y = 1.25 * sbar_fontsize_y;
912
913         // rounded header
914         drawpic(pos, "gfx/hud/sb_scoreboard_tableheader", tmp, '0.5 0.5 0.5', sbar_scoreboard_alpha_bg, DRAWFLAG_NORMAL);
915
916         // table border
917         tmp_y += sbar_border_thickness;
918         tmp_y += body_table_height;
919         drawborderlines(sbar_border_thickness, pos, tmp, '0 0 0', sbar_scoreboard_alpha_bg, DRAWFLAG_NORMAL); // more transparency for the scoreboard
920
921         // separator header/table
922         pos_y += 1.25 * sbar_fontsize_y;
923         tmp_y = sbar_border_thickness;
924         drawfill(pos, tmp, '0 0 0', sbar_scoreboard_alpha_bg, DRAWFLAG_NORMAL);
925
926         pos_y += sbar_border_thickness;
927
928         // table background
929         tmp_y = body_table_height;
930         drawpic_tiled(pos, "gfx/hud/sb_scoreboard_bg", bg_size, tmp, rgb * sbar_color_bg_team, sbar_scoreboard_alpha_bg, DRAWFLAG_NORMAL);
931
932         // anyway, apply some color
933         //drawfill(pos, tmp + '2 0 0', rgb, 0.1, DRAWFLAG_NORMAL);
934
935         // go back to the top to make alternated columns highlighting and to print the strings
936         pos_y -= 1.25 * sbar_fontsize_y;
937         pos_y -= sbar_border_thickness;
938
939         pos += '1 1 0';
940
941         if (sbar_scoreboard_highlight)
942         {
943                 column_dim_y = 1.25 * sbar_fontsize_y; // header
944                 column_dim_y += sbar_border_thickness;
945                 column_dim_y += body_table_height;
946         }
947
948         // print the strings of the columns headers and draw the columns
949         for(i = 0; i < sbar_num_fields; ++i)
950         {
951                 if(sbar_field[i] == SP_SEPARATOR)
952                         break;
953                 column_dim_x = sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
954                 if (sbar_scoreboard_highlight)
955                 {
956                         if (mod(i,2))
957                                 drawfill(pos - '0 1 0' - sbar_fontsize_x / 2 * '1 0 0', column_dim, '0 0 0', sbar_scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
958                 }
959                 drawstring(pos, sbar_title[i], sbar_fontsize, rgb, 1, DRAWFLAG_NORMAL);
960                 pos_x += column_dim_x;
961         }
962         if(sbar_field[i] == SP_SEPARATOR)
963         {
964                 pos_x = xmax;
965                 tmp_y = 0;
966                 for(i = sbar_num_fields-1; i > 0; --i)
967                 {
968                         if(sbar_field[i] == SP_SEPARATOR)
969                                 break;
970
971                         pos_x -= sbar_size[i]*sbar_fontsize_x;
972
973                         if (sbar_scoreboard_highlight)
974                         {
975                                 if (!mod(i,2))
976                                 {
977                                         if (i == sbar_num_fields-1)
978                                                 column_dim_x = sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x / 2 + 1;
979                                         else
980                                                 column_dim_x = sbar_fontsize_x*sbar_size[i] + sbar_fontsize_x;
981                                         drawfill(pos - '0 1 0' - sbar_fontsize_x / 2 * '1 0 0', column_dim, '0 0 0', sbar_scoreboard_alpha_bg * 0.2, DRAWFLAG_NORMAL);
982                                 }
983                         }
984
985                         tmp_x = stringwidth(sbar_title[i], FALSE);
986                         tmp_x = (sbar_size[i] - tmp_x) * sbar_fontsize_x;
987                         drawstring(pos + tmp, sbar_title[i], sbar_fontsize, rgb, 1, DRAWFLAG_NORMAL);
988                         pos_x -= sbar_fontsize_x;
989                 }
990         }
991         
992         pos_x = xmin;
993         pos_y += 1.25 * sbar_fontsize_y; // skip the header
994         pos_y += sbar_border_thickness;
995
996         // fill the table and draw the rows
997         i = 0;
998         if (teamplay)
999                 for(pl = players.sort_next; pl; pl = pl.sort_next)
1000                 {
1001                         if(pl.team != tm.team)
1002                                 continue;
1003                         Sbar_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localentnum - 1), i);
1004                         pos_y += 1.25 * sbar_fontsize_y;
1005                         ++i;
1006                 }
1007         else
1008                 for(pl = players.sort_next; pl; pl = pl.sort_next)
1009                 {
1010                         if(pl.team == COLOR_SPECTATOR)
1011                                 continue;
1012                         Sbar_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localentnum - 1), i);
1013                         pos_y += 1.25 * sbar_fontsize_y;
1014                         ++i;
1015                 }
1016         
1017         if (i == 0)
1018                 pos_y += 1.25 * sbar_fontsize_y; // move to the end of the table
1019         pos_y += 1.25 * sbar_fontsize_y; // move empty row (out of the table)
1020
1021         return pos;
1022 }
1023
1024 float lastpingstime;
1025 float scoreboard_bottom;
1026 void Sbar_DrawScoreboard()
1027 {
1028         vector rgb, pos, tmp, sbar_save;
1029         entity pl, tm;
1030         float specs;
1031         float center_x;
1032
1033         if(time > lastpingstime + 10)
1034         {
1035                 localcmd("pings\n");
1036                 lastpingstime = time;
1037         }
1038
1039         sbwidth = Sbar_GetWidth(6.5 * sbar_fontsize_y);
1040
1041         xmin = 0.5 * (vid_conwidth - sbwidth);
1042         ymin = SCOREBOARD_OFFSET;
1043
1044         xmax = vid_conwidth - xmin;
1045         ymax = vid_conheight - 0.2*vid_conheight;
1046
1047         center_x = xmin + 0.5*sbwidth;
1048
1049         // Initializes position
1050         pos_x = xmin;
1051         pos_y = ymin;
1052         pos_z = 0;
1053
1054         // Heading
1055         drawfont = sbar_bigfont;
1056         drawstringcenter('0 1 0' * ymin, "Scoreboard", '24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1057
1058         pos_y += 24 + 4;
1059         pos_y += sbar_fontsize_y;
1060
1061         drawfont = sbar_font;
1062
1063         sbar_save = sbar;
1064         sbar = '0 0 0';
1065
1066         vector bg_size;
1067         bg_size = drawgetimagesize("gfx/hud/sb_scoreboard_bg");
1068
1069         if(teamplay)
1070         {
1071                 //for(tm = sortedTeams.sort_next; tm; tm = tm.sort_next)
1072                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1073                 {
1074                         if(tm.team == COLOR_SPECTATOR)
1075                                 continue;
1076
1077                         rgb = GetTeamRGB(tm.team);
1078
1079                         Sbar_DrawXNum(sbar + pos - '6.5 0 0' * sbar_fontsize_y + '0 1 0' * sbar_fontsize_y, tm.(teamscores[ts_primary]), 4, sbar_fontsize_y * 1.5, rgb, 0, 1, 1, DRAWFLAG_NORMAL);
1080
1081                         if(ts_primary != ts_secondary)
1082                                 Sbar_DrawXNum(sbar + pos - '4.5 0 0' * sbar_fontsize_y + '0 2.5 0' * sbar_fontsize_y, tm.(teamscores[ts_secondary]), 4, sbar_fontsize_y * 1, rgb, 0, 1, 1, DRAWFLAG_NORMAL);
1083
1084                         pos = Sbar_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1085                 }
1086         }
1087         else
1088         {
1089                 rgb_x = cvar("sbar_color_bg_r");
1090                 rgb_y = cvar("sbar_color_bg_g");
1091                 rgb_z = cvar("sbar_color_bg_b");
1092
1093                 tm = teams.sort_next;
1094
1095                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1096                 {
1097                         if(tm.team == COLOR_SPECTATOR)
1098                                 continue;
1099
1100                         pos = Sbar_Scoreboard_MakeTable(pos, tm, rgb, bg_size);
1101                 }
1102         }
1103
1104         tmp = pos + '0 1.5 0' * sbar_fontsize_y;
1105         pos_y += 3 * sbar_fontsize_y;
1106         specs = 0;
1107         for(pl = players.sort_next; pl; pl = pl.sort_next)
1108         {
1109                 if(pl.team != COLOR_SPECTATOR)
1110                         continue;
1111                 Sbar_PrintScoreboardItem(pos, pl, (pl.sv_entnum == player_localentnum - 1), specs);
1112                 pos_y += 1.25 * sbar_fontsize_y;
1113                 ++specs;
1114         }
1115
1116         if(specs)
1117                 drawstring(tmp, "Spectators", sbar_fontsize, '1 1 1', 1, DRAWFLAG_NORMAL);
1118
1119         string str;
1120         float tl, fl, ll;
1121         str = strcat("playing on ^2", shortmapname, "^7");
1122         tl = getstatf(STAT_TIMELIMIT);
1123         fl = getstatf(STAT_FRAGLIMIT);
1124         ll = getstatf(STAT_LEADLIMIT);
1125         if(gametype == GAME_LMS)
1126         {
1127                 if(tl > 0)
1128                         str = strcat(str, " for up to ^1", ftos(tl), " minutes^7");
1129         }
1130         else
1131         {
1132                 if(tl > 0)
1133                         str = strcat(str, " for ^1", ftos(tl), " minutes^7");
1134                 if(fl > 0)
1135                 {
1136                         if(tl > 0)
1137                                 str = strcat(str, " or");
1138                         if(teamplay)
1139                         {
1140                                 str = strcat(str, " until ^3", ScoreString(teamscores_flags[ts_primary], fl));
1141                                 if(teamscores_label[ts_primary] == "score")
1142                                         str = strcat(str, " points^7");
1143                                 else if(teamscores_label[ts_primary] == "fastest")
1144                                         str = strcat(str, " is beaten^7");
1145                                 else
1146                                         str = strcat(str, " ", teamscores_label[ts_primary]);
1147                         }
1148                         else
1149                         {
1150                                 str = strcat(str, " until ^3", ScoreString(scores_flags[ps_primary], fl));
1151                                 if(scores_label[ps_primary] == "score")
1152                                         str = strcat(str, " points^7");
1153                                 else if(scores_label[ps_primary] == "fastest")
1154                                         str = strcat(str, " is beaten^7");
1155                                 else
1156                                         str = strcat(str, " ", scores_label[ps_primary]);
1157                         }
1158                 }
1159                 if(ll > 0)
1160                 {
1161                         if(tl > 0 || fl > 0)
1162                                 str = strcat(str, " or");
1163                         if(teamplay)
1164                         {
1165                                 str = strcat(str, " until a lead of ^3", ScoreString(teamscores_flags[ts_primary], ll));
1166                                 if(teamscores_label[ts_primary] == "score")
1167                                         str = strcat(str, " points^7");
1168                                 else if(teamscores_label[ts_primary] == "fastest")
1169                                         str = strcat(str, " is beaten^7");
1170                                 else
1171                                         str = strcat(str, " ", teamscores_label[ts_primary]);
1172                         }
1173                         else
1174                         {
1175                                 str = strcat(str, " until a lead of ^3", ScoreString(scores_flags[ps_primary], ll));
1176                                 if(scores_label[ps_primary] == "score")
1177                                         str = strcat(str, " points^7");
1178                                 else if(scores_label[ps_primary] == "fastest")
1179                                         str = strcat(str, " is beaten^7");
1180                                 else
1181                                         str = strcat(str, " ", scores_label[ps_primary]);
1182                         }
1183                 }
1184         }
1185
1186
1187         pos_y += 1.2 * sbar_fontsize_y;
1188         drawcolorcodedstring(pos + '0.5 0 0' * (sbwidth - sbar_fontsize_x * stringwidth(str, TRUE)), str, sbar_fontsize, 0.8, DRAWFLAG_NORMAL);
1189
1190         sbar = sbar_save;
1191         scoreboard_bottom = pos_y + 2 * sbar_fontsize_y;
1192 }
1193
1194 string MakeRaceString(float cp, float mytime, float histime, float lapdelta, string hisname)
1195 {
1196         string col;
1197         string timestr;
1198         string cpname;
1199         string lapstr;
1200         lapstr = "";
1201
1202         if(histime == 0) // goal hit
1203         {
1204                 if(mytime > 0)
1205                 {
1206                         timestr = strcat("+", ftos_decimals(+mytime, 1));
1207                         col = "^1";
1208                 }
1209                 else if(mytime == 0)
1210                 {
1211                         timestr = "+0.0";
1212                         col = "^3";
1213                 }
1214                 else
1215                 {
1216                         timestr = strcat("-", ftos_decimals(-mytime, 1));
1217                         col = "^2";
1218                 }
1219
1220                 if(lapdelta > 0)
1221                 {
1222                         lapstr = strcat(" (-", ftos(lapdelta), "L)");
1223                         col = "^2";
1224                 }
1225                 else if(lapdelta < 0)
1226                 {
1227                         lapstr = strcat(" (+", ftos(-lapdelta), "L)");
1228                         col = "^1";
1229                 }
1230         }
1231         else if(histime > 0) // anticipation
1232         {
1233                 if(mytime >= histime)
1234                         timestr = strcat("+", ftos_decimals(mytime - histime, 1));
1235                 else
1236                         timestr = mmsss(histime * 10);
1237                 col = "^3";
1238         }
1239         else
1240                 col = "^7";
1241
1242         if(cp == 254)
1243                 cpname = "Start line";
1244         else if(cp == 255)
1245                 cpname = "Finish line";
1246         else if(cp)
1247                 cpname = strcat("Intermediate ", ftos(cp));
1248         else
1249                 cpname = "Finish line";
1250
1251         if(histime < 0)
1252                 return strcat(col, cpname);
1253         else if(hisname == "")
1254                 return strcat(col, cpname, " (", timestr, ")");
1255         else
1256                 return strcat(col, cpname, " (", timestr, " ", strcat(hisname, col, lapstr), ")");
1257 }
1258
1259 void Sbar_Score(float margin)
1260 {
1261         float timelimit, minutes, seconds, timeleft, minutesLeft, secondsLeft, distribution, score, desiredPlayerId;
1262         float racemin, racesec, racemsec;
1263         float distsec, distmsec;
1264         vector sbar_save, score_offset, timer_color, offset, distribution_color, minuspos;
1265         entity tm, pl, me;
1266         sbar_save = sbar;
1267
1268         vector bottomright;
1269         bottomright_x = vid_conwidth;
1270         bottomright_y = vid_conheight;
1271         bottomright_z = 0;
1272
1273         vector topright;
1274         topright_x = vid_conwidth;
1275         topright_y = 0;
1276         topright_z = 0;
1277
1278         //get the ID (could be "me", or the player I'm spectating)
1279         if (spectatee_status > 0)
1280                 desiredPlayerId = spectatee_status - 1;
1281         else
1282                 desiredPlayerId = player_localentnum - 1;
1283         me = playerslots[desiredPlayerId];
1284
1285         sbar_y = vid_conheight - (32+12);
1286         sbar_x -= margin;
1287
1288         offset = '0 0 0';
1289
1290         if (vid_conwidth >= 800) {
1291                 score_offset_x = 196;
1292                 score_offset_y = 36;
1293         }
1294         else { // move the scores if vid_conwidth < 800
1295                 score_offset_x = 196;
1296                 score_offset_y = 84;
1297         }
1298         score_offset_z = 0;
1299
1300         if((scores_flags[ps_primary] & SFL_TIME) && !teamplay)
1301         {
1302                 // me vector := [team/connected frags id]
1303
1304                 pl = players.sort_next;
1305                 if(pl == me)
1306                         pl = pl.sort_next;
1307                 if(scores_flags[ps_primary] & SFL_ZERO_IS_WORST)
1308                         if(pl.scores[ps_primary] == 0)
1309                                 pl = world;
1310
1311                 score = me.(scores[ps_primary]);
1312
1313                 racemin = floor(score/600);
1314                 racesec = floor((score - racemin*600)/10);
1315                 racemsec = score - racemin*600 - racesec*10;
1316
1317                 if (pl && ((!(scores_flags[ps_primary] & SFL_ZERO_IS_WORST)) || score)) {
1318                         // distribution display
1319                         distribution = me.(scores[ps_primary]);
1320                         distribution -= pl.(scores[ps_primary]);
1321
1322                         if (distribution < 10 && distribution > -10)
1323                                 distmsec = fabs(distribution);
1324                         else {
1325                                 distsec = floor(fabs(distribution)/10);
1326                                 distmsec = fabs(distribution) - distsec*10;
1327                         }
1328
1329                         if (distribution < 100 && distribution > -100)
1330                                 minuspos = bottomright - element_offset - score_offset + '130 -6 0' + '16 0 0';
1331                         else if (distribution < 1000 && distribution > -1000)
1332                                 minuspos = bottomright - element_offset - score_offset + '130 -6 0';
1333                         else if (distribution < 10000 && distribution > -10000)
1334                                 minuspos = bottomright - element_offset - score_offset + '130 -6 0' - '16 0 0';
1335                         else
1336                                 minuspos = bottomright - element_offset - score_offset + '130 -6 0' - '32 0 0';
1337
1338                         if (distribution <= 0) {
1339                                 distribution_color = '0 1 0';
1340                                 drawpic(minuspos, "gfx/hud/num_minus", '16 16 0', distribution_color, sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1341                         }
1342                         else {
1343                                 distribution_color = '1 0 0';
1344                                 drawpic(minuspos, "gfx/hud/num_plus", '16 16 0', distribution_color, sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1345                         }
1346
1347                         Sbar_DrawXNum(bottomright - element_offset - score_offset + '180 -6 0', distmsec, 1, 16, distribution_color, 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1348                         Sbar_DrawXNum(bottomright - element_offset - score_offset + '112 -6 0', distsec, 4, 16, distribution_color, 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1349                         drawpic(bottomright - element_offset - score_offset + '170 -6 0', "gfx/hud/num_dot", '16 16 0', distribution_color, sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1350                 }
1351
1352                 // big timer
1353                 if (distribution <= 0 || distribution == score) // draw the highlight background behind the timer if we have the lead
1354                         drawpic(bottomright - element_offset - score_offset + '20 10 0', "gfx/hud/sb_highlight_4", '178 28 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
1355
1356                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '166 10 0', racemsec, 1, 30, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1357                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '96 10 0', racesec, -2, 30, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1358                 drawpic(bottomright - element_offset - score_offset + '145 10 0', "gfx/hud/num_dot", '30 30 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1359
1360                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '24 10 0', racemin, -2, 30, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1361                 drawpic(bottomright - element_offset - score_offset + '76 8 0', "gfx/hud/num_colon", '30 30 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1362         }
1363         else {
1364                 if(teamplay)
1365                 {
1366                         // Layout:
1367                         //
1368                         //   team1 team3 team4
1369                         //
1370                         //         TEAM2
1371                         //for(i = 0; i < 4; ++i)
1372
1373                         float max_fragcount;
1374                         max_fragcount = -999;
1375
1376                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1377                         {
1378                                 if(tm.team == COLOR_SPECTATOR || !tm.team_size) // no players? don't display
1379                                         continue;
1380                                 // -32*4 = -128
1381                                 score = tm.(teamscores[ts_primary]);
1382
1383                                 if (score > max_fragcount)
1384                                         max_fragcount = score;
1385
1386                                 if(tm.team == myteam) {
1387                                         if (max_fragcount == score)
1388                                                 Sbar_DrawXNum(bottomright - element_offset - score_offset, score, 4, 34, GetTeamRGB(tm.team) * 0.8, 1, 1, sbar_alpha_fg, DRAWFLAG_NORMAL);
1389                                         else
1390                                                 Sbar_DrawXNum(bottomright - element_offset - score_offset, score, 4, 34, GetTeamRGB(tm.team) * 0.8, 0, 1, sbar_alpha_fg, DRAWFLAG_NORMAL);
1391                                 }
1392                                 else
1393                                 {
1394                                         if (max_fragcount == score)
1395                                                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '132 -6 0' - offset, score, 4, 16, GetTeamRGB(tm.team) * 0.8, 1, 1, sbar_alpha_fg, DRAWFLAG_NORMAL);
1396                                         else
1397                                                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '132 -6 0' - offset, score, 4, 16, GetTeamRGB(tm.team) * 0.8, 0, 1, sbar_alpha_fg, DRAWFLAG_NORMAL);
1398                                         offset_y -= 16;
1399                                 }
1400                         }
1401                 } else {
1402                         // me vector := [team/connected frags id]
1403
1404                         pl = players.sort_next;
1405                         if(pl == me)
1406                                 pl = pl.sort_next;
1407
1408                         if(pl) {
1409                                 distribution = me.(scores[ps_primary]);
1410                                 distribution -= pl.(scores[ps_primary]);
1411                         } else
1412                                 distribution = 0;
1413
1414                         score = me.(scores[ps_primary]);
1415
1416                         if(distribution >= 0)
1417                         {
1418                                 if (distribution != 0) {
1419                                         // draw a + sign in front of the score
1420                                         if (distribution < 10) { drawpic(bottomright - element_offset - score_offset + '132 -6 0' + '32 0 0', "gfx/hud/num_plus", '16 16 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE); }
1421                                         else if (distribution < 100) { drawpic(bottomright - element_offset - score_offset + '132 -6 0' + '16 0 0', "gfx/hud/num_plus", '16 16 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE); }
1422                                         else if (distribution < 1000) { drawpic(bottomright - element_offset - score_offset + '132 -6 0', "gfx/hud/num_plus", '16 16 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE); }
1423                                 }
1424
1425                                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '132 -6 0', distribution, 4, 16, ' 1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1426                                 Sbar_DrawXNum(bottomright - element_offset - score_offset, score, 4, 34, '1 1 1', 1, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1427                         }
1428                         else if(distribution >= -5)
1429                         {
1430                                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '132 -6 0', distribution, 4, 16, ' 1 1 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1431                                 Sbar_DrawXNum(bottomright - element_offset - score_offset, score, 4, 34, '1 1 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1432                         }
1433                         else {
1434                                 Sbar_DrawXNum(bottomright - element_offset - score_offset + '132 -6 0', distribution, 4, 16, ' 1 0 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1435                                 Sbar_DrawXNum(bottomright - element_offset - score_offset, score, 4, 34, '1 0 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1436                         }
1437                 }
1438         }
1439
1440         //draw the remaining or elapsed time
1441         timelimit = getstatf(STAT_TIMELIMIT);
1442         vector bgpos;
1443
1444         timeleft = max(0, timelimit * 60 + getstatf(STAT_GAMESTARTTIME) - time);
1445         timeleft = ceil(timeleft);
1446         minutesLeft = floor(timeleft / 60);
1447         secondsLeft = timeleft - minutesLeft*60;
1448
1449         if(minutesLeft >= 5 || warmup_stage || timelimit == 0) //don't use red or yellow in warmup or when there is no timelimit
1450                 timer_color = '1 1 1'; //white
1451         else if(minutesLeft >= 1)
1452                 timer_color = '1 1 0'; //yellow
1453         else
1454                 timer_color = '1 0 0'; //red
1455
1456         if (cvar("sbar_increment_maptime") || timelimit == 0 || warmup_stage) {
1457                 if (time < getstatf(STAT_GAMESTARTTIME)) {
1458                         //while restart is still active, show 00:00
1459                         minutes = seconds = 0;
1460                 } else {
1461                         float elapsedTime;
1462                         elapsedTime = floor(time - getstatf(STAT_GAMESTARTTIME)); //127
1463                         minutes = floor(elapsedTime / 60);
1464                         seconds = elapsedTime - minutes*60;
1465                 }
1466                 if (minutes < 10)
1467                         bgpos_x = topright_x - 54 - 17 - 12;
1468                 else if (minutes < 100) // nudge the timer background left if more digits are drawn
1469                         bgpos_x = topright_x - 72 - 17 - 12;
1470                 else
1471                         bgpos_x = topright_x - 90 - 17 - 12;
1472                 bgpos_y = 0;
1473                 bgpos_z = 0;
1474         } else {
1475                 minutes = minutesLeft;
1476                 seconds = secondsLeft;
1477                 if (minutes == 0)
1478                 bgpos_x = topright_x - 36 - 7 - 12;
1479                 else if (minutes < 10) // nudge the timer background left if more digits are drawn
1480                         bgpos_x = topright_x - 54 - 17 - 12;
1481                 else if (minutes < 100)
1482                         bgpos_x = topright_x - 72 - 17 - 12;
1483                 else
1484                         bgpos_x = topright_x - 90 - 17 - 12;
1485                 bgpos_y = 0;
1486                 bgpos_z = 0;
1487         }
1488
1489         if (cvar("viewsize") <= 100) { // draw timer background when viewsize <= 100
1490                 if (teamplay)
1491                         drawpic(bgpos, "gfx/hud/sb_timerbg", '120 30 0', GetTeamRGB(myteam) * sbar_color_bg_team, sbar_alpha_bg, DRAWFLAG_NORMAL); // timer bg color = myteam color
1492                 else {
1493                         color_x = cvar("sbar_color_bg_r");
1494                         color_y = cvar("sbar_color_bg_g");
1495                         color_z = cvar("sbar_color_bg_b");
1496
1497                         drawpic(bgpos, "gfx/hud/sb_timerbg", '120 30 0', color, sbar_alpha_bg, DRAWFLAG_NORMAL);
1498                 }
1499         }
1500
1501         if(minutesLeft >= 1 || cvar("sbar_increment_maptime") || timelimit == 0) {
1502                 Sbar_DrawXNum(topright - '103 0 0' + '0 2 0', minutes, 3, 18, timer_color, 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1503                 drawpic(topright - '53 0 0' + '0 1 0', "gfx/hud/num_colon", '18 18 0', timer_color, sbar_alpha_fg, DRAWFLAG_NORMAL);
1504         }
1505         Sbar_DrawXNum(topright - '36 0 0' - '3 0 0' + '0 2 0', seconds, -2, 18, timer_color, 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1506
1507         if(gametype == GAME_RACE || gametype == GAME_CTS)
1508         {
1509                 drawfont = sbar_bigfont;
1510                 float a, t;
1511                 vector m;
1512                 string s, forcetime;
1513
1514                 m = '0.5 0 0' * vid_conwidth + '0 0.25 0' * vid_conheight;
1515
1516                 if(race_checkpointtime)
1517                 {
1518                         a = bound(0, 2 - (time - race_checkpointtime), 1);
1519                         s = "";
1520                         forcetime = "";
1521                         if(a > 0) // just hit a checkpoint?
1522                         {
1523                                 if(race_checkpoint != 254)
1524                                 {
1525                                         if(race_time && race_previousbesttime)
1526                                                 s = MakeRaceString(race_checkpoint, race_time / 10 - race_previousbesttime / 10, 0, 0, race_previousbestname);
1527                                         else
1528                                                 s = MakeRaceString(race_checkpoint, 0, -1, 0, race_previousbestname);
1529                                         if(race_time)
1530                                                 forcetime = mmsss(race_time);
1531                                 }
1532                         }
1533                         else
1534                         {
1535                                 if(race_laptime && race_nextbesttime && race_nextcheckpoint != 254)
1536                                 {
1537                                         a = bound(0, 2 - ((race_laptime + race_nextbesttime/10) - (time + race_penaltyaccumulator/10)), 1);
1538                                         if(a > 0) // next one?
1539                                         {
1540                                                 s = MakeRaceString(race_nextcheckpoint, (time + race_penaltyaccumulator/10) - race_laptime, race_nextbesttime / 10, 0, race_nextbestname);
1541                                         }
1542                                 }
1543                         }
1544
1545                         if(s != "" && a > 0)
1546                         {
1547                                 dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1548                                 drawcolorcodedstring(m - '0 16 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1549                         }
1550
1551                         if(race_penaltytime)
1552                         {
1553                                 a = bound(0, 2 - (time - race_penaltyeventtime), 1);
1554                                 if(a > 0)
1555                                 {
1556                                         s = strcat("^1PENALTY: ", ftos_decimals(race_penaltytime * 0.1, 1), " (", race_penaltyreason, ")");
1557                                         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1558                                         drawcolorcodedstring(m - '0 32 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1559                                 }
1560                         }
1561
1562                         if(forcetime != "")
1563                         {
1564                                 a = bound(0, (time - race_checkpointtime) / 0.5, 1);
1565                                 drawstring_expanding(m - '16 0 0' * stringwidth(forcetime, FALSE), forcetime, '32 32 0', '1 1 1', sbar_alpha_fg, 0, a);
1566                         }
1567                         else
1568                                 a = 1;
1569
1570                         if(race_laptime && race_checkpoint != 255)
1571                         {
1572                                 s = mmsss(10*(time + race_penaltyaccumulator/10 - race_laptime));
1573                                 drawstring(m - '16 0 0' * stringwidth(s, FALSE), s, '32 32 0', '1 1 1', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1574                         }
1575                 }
1576                 else
1577                 {
1578                         if(race_mycheckpointtime)
1579                         {
1580                                 a = bound(0, 2 - (time - race_mycheckpointtime), 1);
1581                                 s = MakeRaceString(race_mycheckpoint, race_mycheckpointdelta / 10, -!race_mycheckpointenemy, race_mycheckpointlapsdelta, race_mycheckpointenemy);
1582                                 dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1583                                 drawcolorcodedstring(m - '0 16 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1584                         }
1585                         if(race_othercheckpointtime && race_othercheckpointenemy != "")
1586                         {
1587                                 a = bound(0, 2 - (time - race_othercheckpointtime), 1);
1588                                 s = MakeRaceString(race_othercheckpoint, -race_othercheckpointdelta / 10, -!race_othercheckpointenemy, race_othercheckpointlapsdelta, race_othercheckpointenemy);
1589                                 dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1590                                 drawcolorcodedstring(m - '0 0 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1591                         }
1592
1593                         if(race_penaltytime && !race_penaltyaccumulator)
1594                         {
1595                                 t = race_penaltytime * 0.1 + race_penaltyeventtime;
1596                                 a = bound(0, (1 + t - time), 1);
1597                                 if(a > 0)
1598                                 {
1599                                         if(time < t)
1600                                                 s = strcat("^1PENALTY: ", ftos_decimals(t - time, 1), " (", race_penaltyreason, ")");
1601                                         else
1602                                                 s = strcat("^2PENALTY: 0.0 (", race_penaltyreason, ")");
1603                                         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1604                                         drawcolorcodedstring(m - '0 32 0' - '8 0 0' * stringwidth(s, TRUE), s, '16 16 0', sbar_alpha_fg * a, DRAWFLAG_NORMAL);
1605                                 }
1606                         }
1607                 }
1608
1609                 drawfont = sbar_font;
1610         }
1611
1612         sbar = sbar_save;
1613 }
1614
1615 float Sbar_WouldDrawScoreboard ()
1616 {
1617         if (sb_showscores)
1618                 return 1;
1619         else if (intermission == 1)
1620                 return 1;
1621         else if (intermission == 2)
1622                 return 1;
1623         else if (getstati(STAT_HEALTH) <= 0 && cvar("cl_deathscoreboard"))
1624                 return 1;
1625         else if(sb_showscores_force)
1626                 return 1;
1627         return 0;
1628 }
1629
1630 void CSQC_Strength_Timer() {
1631         vector bottom;
1632
1633         bottom_x = vid_conwidth/2;
1634         bottom_y = vid_conheight;
1635         bottom_z = 0;
1636
1637         float stat_items, dt;
1638         stat_items = getstati(STAT_ITEMS);
1639         /*
1640         if not(stat_items & IT_STRENGTH)
1641                 if not(stat_items & IT_INVINCIBLE)
1642                         return;
1643         */
1644
1645         if (getstati(STAT_HEALTH) <= 0)
1646                 return;
1647
1648         vector picsize;
1649         float strength_time, invincibility_time, countdown_fontsize;
1650
1651         picsize = '22 22 0';
1652         countdown_fontsize = 18;
1653
1654         //strength
1655         strength_time = getstatf(STAT_STRENGTH_FINISHED);
1656         invincibility_time = getstatf(STAT_INVINCIBLE_FINISHED);
1657
1658         if (strength_time) {
1659                 dt = strength_time - time;
1660                 if(dt > 0)
1661                 {
1662                         if(dt < 5)
1663                         {
1664                                 drawpic_expanding_two(bottom + '192 -46 0', "gfx/hud/sb_str", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE,
1665                                         bound(0, (ceil(dt) - dt) / 0.5, 1));
1666                         }
1667                         else
1668                         {
1669                                 drawpic(bottom + '192 -46 0', "gfx/hud/sb_str", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1670                         }
1671                         Sbar_DrawXNum(bottom + '152 -44 0', ceil(dt), 2, countdown_fontsize, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1672                 }
1673                 else if(dt > -1)
1674                 {
1675                         drawpic_expanding(bottom + '192 -46 0', "gfx/hud/sb_str", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE,
1676                                 bound(0, -dt / 0.5, 1));
1677                 }
1678         }
1679
1680         //invincibility
1681         if (invincibility_time) {
1682                 dt = invincibility_time - time;
1683                 if(dt > 0)
1684                 {
1685                         if(dt < 5)
1686                         {
1687                                 drawpic_expanding_two(bottom + '192 -24 0', "gfx/hud/sb_invinc", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE,
1688                                         bound(0, (ceil(dt) - dt) / 0.5, 1));
1689                         }
1690                         else
1691                         {
1692                                 drawpic(bottom + '192 -24 0', "gfx/hud/sb_invinc", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE);
1693                         }
1694                         Sbar_DrawXNum(bottom + '152 -22 0', ceil(dt), 2, countdown_fontsize, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
1695                 }
1696                 else if(dt > -1)
1697                 {
1698                         drawpic_expanding(bottom + '192 -24 0', "gfx/hud/sb_invinc", picsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_ADDITIVE,
1699                                 bound(0, -dt / 0.5, 1));
1700                 }
1701         }
1702 }
1703
1704 #define CENTERPRINT_MAX_LINES 30
1705 string centerprint_messages[CENTERPRINT_MAX_LINES];
1706 float centerprint_width[CENTERPRINT_MAX_LINES];
1707 vector centerprint_start;
1708 float centerprint_expire;
1709 float centerprint_num;
1710 float centerprint_offset_hint;
1711 vector centerprint_fontsize;
1712
1713 void centerprint(string strMessage)
1714 {
1715         float i, j, n, hcount;
1716         string s;
1717
1718         centerprint_fontsize = Sbar_GetFontsize("scr_centersize");
1719
1720         centerprint_expire = min(centerprint_expire, time); // if any of the returns happens, this message will fade out
1721
1722         if(cvar("scr_centertime") <= 0)
1723                 return;
1724
1725         if(strMessage == "")
1726                 return;
1727
1728         // strip trailing newlines
1729         j = strlen(strMessage) - 1;
1730         while(substring(strMessage, j, 1) == "\n" && j >= 0)
1731                 j = j - 1;
1732         strMessage = substring(strMessage, 0, j + 1);
1733
1734         if(strMessage == "")
1735                 return;
1736
1737         // strip leading newlines and remember them, they are a hint that the message should be lower on the screen
1738         j = 0;
1739         while(substring(strMessage, j, 1) == "\n" && j < strlen(strMessage))
1740                 j = j + 1;
1741         strMessage = substring(strMessage, j, strlen(strMessage) - j);
1742         centerprint_offset_hint = j;
1743
1744         if(strMessage == "")
1745                 return;
1746
1747         // if we get here, we have a message. Initialize its height.
1748         centerprint_num = 0;
1749
1750         n = tokenizebyseparator(strMessage, "\n");
1751         i = hcount = 0;
1752         for(j = 0; j < n; ++j)
1753         {
1754                 getWrappedLine_remaining = argv(j);
1755                 while(getWrappedLine_remaining)
1756                 {
1757                         s = getWrappedLine(vid_conwidth * 0.75 / centerprint_fontsize_x, stringwidth_colors);
1758                         if(centerprint_messages[i])
1759                                 strunzone(centerprint_messages[i]);
1760                         centerprint_messages[i] = strzone(s);
1761                         centerprint_width[i] = stringwidth(s, TRUE);
1762                         ++i;
1763
1764                         // half height for empty lines looks better
1765                         if(s == "")
1766                                 hcount += 0.5;
1767                         else
1768                                 hcount += 1;
1769
1770                         if(i >= CENTERPRINT_MAX_LINES)
1771                                 break;
1772                 }
1773         }
1774
1775         float h, havail;
1776         h = centerprint_fontsize_y*hcount;
1777
1778         havail = vid_conheight;
1779         if(cvar("con_chatpos") < 0)
1780                 havail -= (-cvar("con_chatpos") + cvar("con_chat")) * cvar("con_chatsize"); // avoid overlapping chat
1781         if(havail > vid_conheight - 70)
1782                 havail = vid_conheight - 70; // avoid overlapping HUD
1783
1784         centerprint_start_x = 0;
1785
1786 #if 0
1787         float forbiddenmin, forbiddenmax, allowedmin, allowedmax, preferred;
1788
1789         // here, the centerprint would cover the crosshair. REALLY BAD.
1790         forbiddenmin = vid_conheight * 0.5 - h - 16;
1791         forbiddenmax = vid_conheight * 0.5 + 16;
1792
1793         allowedmin = scoreboard_bottom;
1794         allowedmax = havail - h;
1795         preferred = (havail - h)/2;
1796
1797
1798         // possible orderings (total: 4! / 4 = 6)
1799         //  allowedmin allowedmax forbiddenmin forbiddenmax
1800         //  forbiddenmin forbiddenmax allowedmin allowedmax
1801         if(allowedmax < forbiddenmin || allowedmin > forbiddenmax)
1802         {
1803                 // forbidden doesn't matter in this case
1804                 centerprint_start_y = bound(allowedmin, preferred, allowedmax);
1805         }
1806         //  allowedmin forbiddenmin allowedmax forbiddenmax
1807         else if(allowedmin < forbiddenmin && allowedmax < forbiddenmax)
1808         {
1809                 centerprint_start_y = bound(allowedmin, preferred, forbiddenmin);
1810         }
1811         //  allowedmin forbiddenmin forbiddenmax allowedmax
1812         else if(allowedmin < forbiddenmin)
1813         {
1814                 // make sure the forbidden zone is not covered
1815                 if(preferred > (forbiddenmin + forbiddenmax) * 0.5)
1816                         centerprint_start_y = bound(allowedmin, preferred, forbiddenmin);
1817                 else
1818                         centerprint_start_y = bound(forbiddenmax, preferred, allowedmin);
1819         }
1820         //  forbiddenmin allowedmin allowedmax forbiddenmax
1821         else if(allowedmax < forbiddenmax)
1822         {
1823                 // it's better to leave the allowed zone (overlap with scoreboard) than
1824                 // to cover the forbidden zone (crosshair)
1825                 if(preferred > (forbiddenmin + forbiddenmax) * 0.5)
1826                         centerprint_start_y = forbiddenmax;
1827                 else
1828                         centerprint_start_y = forbiddenmin;
1829         }
1830         //  forbiddenmin allowedmin forbiddenmax allowedmax
1831         else
1832         {
1833                 centerprint_start_y = bound(forbiddenmax, preferred, allowedmax);
1834         }
1835 #else
1836         centerprint_start_y =
1837                 min(
1838                         max(
1839                                 max(scoreboard_bottom, vid_conheight * 0.5 + 16),
1840                                 (havail - h)/2
1841                         ),
1842                         havail - h
1843                 );
1844 #endif
1845
1846         centerprint_num = i;
1847         centerprint_expire = time + cvar("scr_centertime");
1848 }
1849
1850 void Sbar_DrawCenterPrint (void)
1851 {
1852         float i;
1853         vector pos;
1854         string ts;
1855         float a;
1856
1857         //if(time > centerprint_expire)
1858         //      return;
1859
1860         //a = bound(0, 1 - 2 * (time - centerprint_expire), 1);
1861         a = bound(0, 1 - 4 * (time - centerprint_expire), 1);
1862         //sz = 1.2 / (a + 0.2);
1863
1864         if(a <= 0)
1865                 return;
1866
1867         pos = centerprint_start;
1868         for (i=0; i<centerprint_num; i = i + 1)
1869         {
1870                 pos_x = (vid_conwidth - centerprint_fontsize_x * centerprint_width[i]) * 0.5;
1871                 ts = centerprint_messages[i];
1872                 if (ts != "")
1873                 {
1874                         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1875                         drawcolorcodedstring(pos, ts, centerprint_fontsize, a, DRAWFLAG_NORMAL);
1876                         //  - '0 0.5 0' * (sz - 1) * centerprint_fontsize_x - '0.5 0 0' * (sz - 1) * centerprint_width[i] * centerprint_fontsize_y, centerprint_fontsize * sz
1877                         pos_y = pos_y + centerprint_fontsize_y;
1878                 }
1879                 else
1880                         // half height for empty lines looks better
1881                         pos_y = pos_y + centerprint_fontsize_y * 0.5;
1882         }
1883 }
1884
1885 vector Sbar_DrawNoteLine(vector offset, string s)
1886 {
1887         dummyfunction(0, 0, 0, 0, 0, 0, 0, 0); // work around DP bug (set OFS_PARAM5 to 0)
1888         drawcolorcodedstring(
1889                 offset - sbar_fontsize_x * '1 0 0' * stringwidth(s, TRUE),
1890                 s,
1891                 sbar_fontsize,
1892                 sbar_alpha_fg,
1893                 0
1894         );
1895         return offset + sbar_fontsize_y * '0 1 0';
1896 }
1897
1898 void Sbar_DrawPressedKeys(void)
1899 {
1900         vector pos, bgsize;
1901         float pressedkeys;
1902
1903         pos = stov(cvar_string("cl_showpressedkeys_position"));
1904
1905         bgsize = '126 75 0';
1906
1907         pos = '1 0 0' * (vid_conwidth - bgsize_x) * pos_x
1908             + '0 1 0' * (vid_conheight - bgsize_y) * pos_y;
1909         pos -= '-15 -6 0'; // adjust to the origin of these numbers
1910
1911         pressedkeys = getstatf(STAT_PRESSED_KEYS);
1912         drawpic(pos + '-15   -6   0', "gfx/hud/keys/key_bg.tga",           bgsize, '1 1 1', .1, DRAWFLAG_NORMAL);
1913         drawpic(pos + ' 83.5  9   0', ((pressedkeys & KEY_CROUCH) ? "gfx/hud/keys/key_crouch_inv.tga" : "gfx/hud/keys/key_crouch.tga"), ' 24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1914         drawpic(pos + ' 32   -1.5 0', ((pressedkeys & KEY_FORWARD) ? "gfx/hud/keys/key_forward_inv.tga" : "gfx/hud/keys/key_forward.tga"),  ' 32 32 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1915         drawpic(pos + '-11.5  9   0', ((pressedkeys & KEY_JUMP) ? "gfx/hud/keys/key_jump_inv.tga" : "gfx/hud/keys/key_jump.tga"),     ' 24 24 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1916         drawpic(pos + ' -1   32   0', ((pressedkeys & KEY_LEFT) ? "gfx/hud/keys/key_left_inv.tga" : "gfx/hud/keys/key_left.tga"),     ' 32 32 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1917         drawpic(pos + ' 32   32   0', ((pressedkeys & KEY_BACKWARD) ? "gfx/hud/keys/key_backward_inv.tga" : "gfx/hud/keys/key_backward.tga"), ' 32 32 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1918         drawpic(pos + ' 65   32   0', ((pressedkeys & KEY_RIGHT) ? "gfx/hud/keys/key_right_inv.tga" : "gfx/hud/keys/key_right.tga"),    ' 32 32 0', '1 1 1', 1, DRAWFLAG_NORMAL);
1919 }
1920
1921 void Sbar_ShowSpeed(void)
1922 {
1923         vector numsize;
1924         float pos;
1925         string speed;
1926
1927         if (cvar("cl_showspeed_z") == 1)
1928                 speed = ftos(floor(vlen(pmove_vel) + 0.5));
1929         else
1930                 speed = ftos(floor(vlen(pmove_vel - pmove_vel_z * '0 0 1') + 0.5));
1931
1932         pos = cvar("cl_showspeed_position");
1933         numsize_x = numsize_y = cvar("cl_showspeed_size");
1934         pos = (vid_conheight - numsize_y) * pos;
1935
1936         drawstringcenter('1 0 0' + pos * '0 1 0', speed, numsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
1937 }
1938
1939 void Sbar_DrawAccuracyStats()
1940 {
1941         float i, count_hitscan, count_splash, row;  // count is the number of 'colums'
1942         float weapon_hit, weapon_damage, weapon_stats;
1943         float left_border;  // position where the weapons start, the description is in the border
1944         vector fill_colour, fill_size;
1945         vector pos;
1946
1947         float col_margin = 20;  // pixels between the columns
1948         float row_margin = 20;  // pixels between the rows
1949
1950         fill_size_x = 5 * sbar_fontsize_x;  // width of the background
1951         fill_size_y = 10 * sbar_fontsize_y;  // height of the background
1952
1953         drawfont = sbar_bigfont;
1954
1955         pos_x = 0;
1956         pos_y = SCOREBOARD_OFFSET;
1957         pos_z = 0;
1958         drawstringcenter(pos, "Weapon Accuracy", 2 * sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
1959         left_border = col_margin + 11 * sbar_fontsize_x;
1960
1961         if(warmup_stage)
1962         {
1963                 pos_y += 40;
1964                 if(mod(time, 1) >= 0.4)
1965                         drawstringcenter(pos, "Stats are not tracked during warmup stage", sbar_fontsize, '1 1 0', sbar_alpha_fg, DRAWFLAG_NORMAL);
1966
1967                 return;
1968         }
1969
1970         float top_border_hitscan = SCOREBOARD_OFFSET + 55;  // position where the hitscan row starts: pixels down the screen
1971         Sbar_DrawAccuracyStats_Description_Hitscan('1 0 0' * col_margin + '0 1 0' * top_border_hitscan);
1972
1973         float top_border_splash = SCOREBOARD_OFFSET + 175;  // position where the splash row starts: pixels down the screen
1974         Sbar_DrawAccuracyStats_Description_Splash('1 0 0' * col_margin + '0 1 0' * top_border_splash);
1975
1976         for(i = WEP_FIRST; i <= WEP_LAST; ++i)
1977         {
1978                 weapon_hit = weapon_hits[i];
1979                 weapon_damage = weapon_fired[i];
1980                 self = get_weaponinfo(i);
1981
1982                 //if ((weapon_number != 42))  // print them all :)
1983                 if ((self.weapon_type == WEP_TYPE_SPLASH) && (weapon_damage))
1984                 {
1985                         weapon_stats = rint(100 * weapon_hit / weapon_damage);
1986
1987                         fill_colour_x = 1 - 0.015 * weapon_stats;
1988                         fill_colour_y = 1 - 0.015 * (100 - weapon_stats);
1989
1990 //                      how the background colour is calculated
1991 //                      %       red     green   red_2           green_2
1992 //                      0       1       0       1 - % * 0.015   1 - (100 - %) * 0.015
1993 //                      10      0.85    0       1 - % * 0.015   1 - (100 - %) * 0.015
1994 //                      20      0.70    0       1 - % * 0.015   1 - (100 - %) * 0.015
1995 //                      30      0.55    0       1 - % * 0.015   1 - (100 - %) * 0.015
1996 //                      40      0.40    0.10    1 - % * 0.015   1 - (100 - %) * 0.015
1997 //                      50      0.25    0.25    1 - % * 0.015   1 - (100 - %) * 0.015
1998 //                      60      0.10    0.40    1 - % * 0.015   1 - (100 - %) * 0.015
1999 //                      70      0       0.55    1 - % * 0.015   1 - (100 - %) * 0.015
2000 //                      80      0       0.70    1 - % * 0.015   1 - (100 - %) * 0.015
2001 //                      90      0       0.85    1 - % * 0.015   1 - (100 - %) * 0.015
2002 //                      100     0       1       1 - % * 0.015   1 - (100 - %) * 0.015
2003
2004                         if ((left_border + count_splash * (fill_size_x + col_margin) + fill_size_x) >= vid_conwidth)
2005                         {
2006                                 count_splash = 0;
2007                                 ++row;
2008                                 Sbar_DrawAccuracyStats_Description_Splash('1 0 0' * col_margin + '0 1 0' * (top_border_splash + row * (fill_size_y + row_margin)));
2009                         }
2010
2011                         pos_x = left_border + count_splash * (fill_size_x + col_margin);
2012                         pos_y = top_border_splash + row * (fill_size_y + row_margin);
2013
2014                         // background
2015                         drawpic(pos, "gfx/hud/sb_accuracy", fill_size , fill_colour, sbar_alpha_bg, DRAWFLAG_NORMAL);
2016                         drawborderlines(sbar_border_thickness, pos, fill_size, '0 0 0', sbar_alpha_bg, DRAWFLAG_NORMAL);
2017
2018                         // the weapon
2019                         drawpic(pos, strcat("gfx/hud/inv_weapon", ftos(i-1)), '1 0.5 0' * fill_size_x , '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2020
2021                         // the amount of shots fired or max damage
2022                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 3 0' * sbar_fontsize_y, ftos(weapon_damage), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2023
2024                         // the amount of hits or actual damage
2025                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 5 0' * sbar_fontsize_y, ftos(weapon_hit), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2026
2027                         // the accuracy
2028                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 7 0' * sbar_fontsize_y, strcat(ftos(weapon_stats),"%"), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2029
2030                         // the amount of shots missed or damage wasted
2031                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 9 0' * sbar_fontsize_y, ftos(weapon_damage - weapon_hit), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2032
2033                         ++count_splash;
2034                 }
2035                 else if ((self.weapon_type == WEP_TYPE_HITSCAN) && (weapon_damage))
2036                 {
2037                         weapon_stats = rint(100 * weapon_hit / weapon_damage);
2038
2039                         fill_colour_x = 1 - 0.015 * weapon_stats;
2040                         fill_colour_y = 1 - 0.015 * (100 - weapon_stats);
2041
2042 //                      how the background colour is calculated
2043 //                      %       red     green   red_2           green_2
2044 //                      0       1       0       1 - % * 0.015   1 - (100 - %) * 0.015
2045 //                      10      0.85    0       1 - % * 0.015   1 - (100 - %) * 0.015
2046 //                      20      0.70    0       1 - % * 0.015   1 - (100 - %) * 0.015
2047 //                      30      0.55    0       1 - % * 0.015   1 - (100 - %) * 0.015
2048 //                      40      0.40    0.10    1 - % * 0.015   1 - (100 - %) * 0.015
2049 //                      50      0.25    0.25    1 - % * 0.015   1 - (100 - %) * 0.015
2050 //                      60      0.10    0.40    1 - % * 0.015   1 - (100 - %) * 0.015
2051 //                      70      0       0.55    1 - % * 0.015   1 - (100 - %) * 0.015
2052 //                      80      0       0.70    1 - % * 0.015   1 - (100 - %) * 0.015
2053 //                      90      0       0.85    1 - % * 0.015   1 - (100 - %) * 0.015
2054 //                      100     0       1       1 - % * 0.015   1 - (100 - %) * 0.015
2055
2056                         if ((left_border + count_hitscan * (fill_size_x + col_margin) + fill_size_x + cvar("stats_right_margin")) >= vid_conwidth)
2057                         {
2058                                 count_hitscan = 0;
2059                                 ++row;
2060                                 Sbar_DrawAccuracyStats_Description_Hitscan('1 0 0' * col_margin + '0 1 0' * (top_border_hitscan + row * (fill_size_y + row_margin)));
2061                         }
2062
2063                         pos_x = left_border + count_hitscan * (fill_size_x + col_margin);
2064                         pos_y = top_border_hitscan + row * (fill_size_y + row_margin);
2065
2066                         // background
2067                         drawpic(pos, "gfx/hud/sb_accuracy", fill_size , fill_colour, sbar_alpha_bg, DRAWFLAG_NORMAL);
2068                         drawborderlines(sbar_border_thickness, pos, fill_size, '0 0 0', sbar_alpha_bg, DRAWFLAG_NORMAL);
2069
2070                         // the weapon
2071                         drawpic(pos, strcat("gfx/hud/inv_weapon", ftos(i-1)), '1 0.5 0' * fill_size_x , '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2072
2073                         // the amount of shots fired or max damage
2074                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 3 0' * sbar_fontsize_y, ftos(weapon_damage), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2075
2076                         // the amount of hits or actual damage
2077                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 5 0' * sbar_fontsize_y, ftos(weapon_hit), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2078
2079                         // the accuracy
2080                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 7 0' * sbar_fontsize_y, strcat(ftos(weapon_stats),"%"), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2081
2082                         // the amount of shots missed or damage wasted
2083                         drawstringright(pos + '4.5 0 0' * sbar_fontsize_x + '0 9 0' * sbar_fontsize_y, ftos(weapon_damage - weapon_hit), sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2084
2085                         ++count_hitscan;
2086                 }
2087         }
2088 }
2089
2090 void Sbar_DrawAccuracyStats_Description_Hitscan(vector position)
2091 {
2092         drawfont = sbar_font;
2093          // hitscan stats
2094         drawstring(position + '0 3 0' * sbar_fontsize_y, "Shots fired:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2095         drawstring(position + '0 5 0' * sbar_fontsize_y, "Shots hit:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2096         drawstring(position + '0 7 0' * sbar_fontsize_y, "Accuracy:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2097         drawstring(position + '0 9 0' * sbar_fontsize_y, "Shots missed:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2098 }
2099
2100 void Sbar_DrawAccuracyStats_Description_Splash(vector position)
2101 {
2102         //splash stats
2103         drawfont = sbar_font;
2104         drawstring(position + '0 3 0' * sbar_fontsize_y, "Maximum damage:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2105         drawstring(position + '0 5 0' * sbar_fontsize_y, "Actual damage:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2106         drawstring(position + '0 7 0' * sbar_fontsize_y, "Accuracy:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2107         drawstring(position + '0 9 0' * sbar_fontsize_y, "Damage wasted:", sbar_fontsize, '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2108 }
2109
2110 void drawstringright(vector position, string text, vector scale, vector rgb, float alpha, float flag)
2111 {
2112         position_x -= 2 / 3 * strlen(text) * scale_x;
2113         drawstring(position, text, scale, rgb, alpha, flag);
2114 }
2115
2116 void drawstringcenter(vector position, string text, vector scale, vector rgb, float alpha, float flag)
2117 {
2118         position_x = 0.5 * (vid_conwidth - 0.6025 * strlen(text) * scale_x);
2119         drawstring(position, text, scale, rgb, alpha, flag);
2120 }
2121
2122 float GetAmmoStat(float i)
2123 {
2124         switch(i)
2125         {
2126                 case 0: return STAT_SHELLS;
2127                 case 1: return STAT_NAILS;
2128                 case 2: return STAT_ROCKETS;
2129                 case 3: return STAT_CELLS;
2130                 case 4: return STAT_FUEL;
2131                 default: return -1;
2132         }
2133 }
2134
2135 float GetAmmoItemCode(float i)
2136 {
2137         switch(i)
2138         {
2139                 case 0: return IT_SHELLS;
2140                 case 1: return IT_NAILS;
2141                 case 2: return IT_ROCKETS;
2142                 case 3: return IT_CELLS;
2143                 case 4: return IT_FUEL;
2144                 default: return -1;
2145         }
2146 }
2147
2148 string GetAmmoPicture(float i)
2149 {
2150         switch(i)
2151         {
2152                 case 0: return "gfx/hud/sb_shells";
2153                 case 1: return "gfx/hud/sb_bullets";
2154                 case 2: return "gfx/hud/sb_rocket";
2155                 case 3: return "gfx/hud/sb_cells";
2156                 case 4: return "gfx/hud/sb_fuel";
2157                 default: return "";
2158         }
2159 }
2160
2161 void Sbar_Draw (void)
2162 {
2163         // vectors for top right, bottom right, bottom and bottom left corners
2164
2165         vector topright;
2166         vector bottom;
2167         vector bottomright;
2168         vector bottomleft;
2169
2170         topright_x = vid_conwidth;
2171         topright_y = 0;
2172         topright_z = 0;
2173
2174         bottom_x = vid_conwidth/2;
2175         bottom_y = vid_conheight;
2176         bottom_z = 0;
2177
2178         bottomright_x = vid_conwidth;
2179         bottomright_y = vid_conheight;
2180         bottomright_z = 0;
2181
2182         bottomleft_x = 0;
2183         bottomleft_y = vid_conheight;
2184         bottomleft_z = 0;
2185
2186         sbar_alpha_bg = cvar("sbar_alpha_bg");
2187         sbar_border_thickness = bound(0, cvar("sbar_border_thickness"), 5);
2188         sbar_color_bg_team = cvar("sbar_color_bg_team");
2189         sbar_scoreboard_alpha_bg = cvar("sbar_scoreboard_alpha_bg");
2190         sbar_scoreboard_highlight = cvar("sbar_scoreboard_highlight");
2191
2192         float i;
2193         float weapon_stats;
2194         float x, fade;
2195         float stat_items, stat_weapons;
2196
2197         weapon_stats = getstati(STAT_DAMAGE_HITS);
2198         weapon_number = weapon_stats & 63;
2199         weapon_hits[weapon_number] = rint(weapon_stats / 64);
2200
2201         weapon_stats = getstati(STAT_DAMAGE_FIRED);
2202         weapon_number = weapon_stats & 63;
2203         weapon_fired[weapon_number] = rint(weapon_stats / 64);
2204
2205         vector o; o = '1 0 0' * vid_conwidth;
2206         o_y = 28; // move spectator text slightly down to prevent overlapping the timer
2207
2208         string s;
2209         vector pos;
2210         pos = '0 0 0';
2211
2212         sbar_fontsize = Sbar_GetFontsize("sbar_fontsize");
2213
2214         if(spectatee_status && !intermission)
2215         {
2216                 if(spectatee_status == -1)
2217                         s = "^1Observing";
2218                 else
2219                         s = GetPlayerName(spectatee_status - 1);
2220                 // spectated player name between HUD and chat area, aligned to the left
2221                 pos_x = bottomleft_x;
2222                 pos_y = bottom_y - 61;
2223                 s = textShortenToWidth(s, vid_conwidth/2.5/sbar_fontsize_x, stringwidth_colors);
2224                 drawcolorcodedstring(pos, s, sbar_fontsize, 1, DRAWFLAG_NORMAL);
2225
2226                 // spectator text in the upper right corner
2227                 if(spectatee_status == -1)
2228                         s = strcat("^1Press ^3", getcommandkey("primary fire", "+attack"), "^1 to spectate");
2229                 else
2230                         s = strcat("^1Press ^3", getcommandkey("primary fire", "+attack"), "^1 for another player");
2231                 o = Sbar_DrawNoteLine(o, s);
2232
2233                 if(spectatee_status == -1)
2234                         s = strcat("^1Use ^3", getcommandkey("next weapon", "weapnext"), "^1 or ^3", getcommandkey("previous weapon", "weapprev"), "^1 to change the speed");
2235                 else
2236                         s = strcat("^1Press ^3", getcommandkey("secondary fire", "+attack2"), "^1 to observe");
2237                 o = Sbar_DrawNoteLine(o, s);
2238
2239                 s = strcat("^1Press ^3", getcommandkey("server info", "+show_info"), "^1 for gamemode info");
2240                 o = Sbar_DrawNoteLine(o, s);
2241
2242                 if(gametype == GAME_ARENA)
2243                         s = "^1Wait for your turn to join";
2244                 else if(gametype == GAME_LMS)
2245                 {
2246                         entity sk;
2247                         sk = playerslots[player_localentnum - 1];
2248                         if(sk.(scores[ps_primary]) >= 666)
2249                                 s = "^1Match has already begun";
2250                         else if(sk.(scores[ps_primary]) > 0)
2251                                 s = "^1You have no more lives left";
2252                         else
2253                                 s = strcat("^1Press ^3", getcommandkey("jump", "+jump"), "^1 to join");
2254                 }
2255                 else
2256                         s = strcat("^1Press ^3", getcommandkey("jump", "+jump"), "^1 to join");
2257                 o = Sbar_DrawNoteLine(o, s);
2258
2259                 //show restart countdown:
2260                 if (time < getstatf(STAT_GAMESTARTTIME)) {
2261                         float countdown;
2262                         //we need to ceil, otherwise the countdown would be off by .5 when using round()
2263                         countdown = ceil(getstatf(STAT_GAMESTARTTIME) - time);
2264                         s = strcat("^1Game starts in ^3", ftos(countdown), "^1 seconds");
2265                         o = Sbar_DrawNoteLine(o, s);
2266                 }
2267         }
2268         if(warmup_stage && !intermission)
2269         {
2270                 s = "^2Currently in ^1warmup^2 stage!";
2271                 o = Sbar_DrawNoteLine(o, s);
2272         }
2273
2274         // move more important stuff more to the middle so its more visible
2275         o_y = vid_conheight * 0.66;
2276
2277         string blinkcolor;
2278         if(mod(time, 1) >= 0.5)
2279                 blinkcolor = "^1";
2280         else
2281                 blinkcolor = "^3";
2282
2283         if(ready_waiting && !intermission)
2284         {
2285                 if(ready_waiting_for_me)
2286                 {
2287                         if(warmup_stage)
2288                                 s = strcat(blinkcolor, "Press ^3", getcommandkey("ready", "ready"), blinkcolor, " to end warmup");
2289                         else
2290                                 s = strcat(blinkcolor, "Press ^3", getcommandkey("ready", "ready"), blinkcolor, " once you are ready");
2291                 }
2292                 else
2293                 {
2294                         if(warmup_stage)
2295                                 s = strcat("^2Waiting for others to ready up to end warmup...");
2296                         else
2297                                 s = strcat("^2Waiting for others to ready up...");
2298                 }
2299                 o = Sbar_DrawNoteLine(o, s);
2300         }
2301         else if(warmup_stage && !intermission)
2302         {
2303                 s = strcat("^2Press ^3", getcommandkey("ready", "ready"), "^2 to end warmup");
2304                 o = Sbar_DrawNoteLine(o, s);
2305         }
2306         if(vote_waiting)
2307         {
2308                 s = strcat("^2A vote has been called for ^1", vote_called_vote);
2309                 o = Sbar_DrawNoteLine(o, s);
2310
2311                 if(vote_waiting_for_me)
2312                 {
2313                         s = strcat(blinkcolor, "Press ^3", getcommandkey("vote yes", "vyes"), blinkcolor, " to accept");
2314                         o = Sbar_DrawNoteLine(o, s);
2315
2316                         s = strcat(blinkcolor, "Press ^3", getcommandkey("vote no", "vno"), blinkcolor, " to reject");
2317                         o = Sbar_DrawNoteLine(o, s);
2318
2319                         s = strcat(blinkcolor, "Press ^3", getcommandkey("vote abstain", "vabstain"), blinkcolor, " to abstain");
2320                         o = Sbar_DrawNoteLine(o, s);
2321                 }
2322                 else
2323                 {
2324                         s = strcat("^2Waiting for others to vote...");
2325                         o = Sbar_DrawNoteLine(o, s);
2326                 }
2327         }
2328         if(teamplay && !intermission && !spectatee_status)
2329         {
2330                 entity tm;
2331                 float ts_min, ts_max;
2332                 tm = teams.sort_next;
2333                 if (tm)
2334                 {
2335                         for(; tm.sort_next; tm = tm.sort_next)
2336                         {
2337                                 if(!tm.team_size || tm.team == COLOR_SPECTATOR)
2338                                         continue;
2339                                 if(!ts_min) ts_min = tm.team_size;
2340                                 else ts_min = min(ts_min, tm.team_size);
2341                                 if(!ts_max) ts_max = tm.team_size;
2342                                 else ts_max = max(ts_max, tm.team_size);
2343                         }
2344                         if ((ts_max - ts_min) > 1)
2345                         {
2346                                 s = strcat(blinkcolor, "Teamnumbers are unbalanced!");
2347                                 tm = GetTeam(myteam, false);
2348                                 if (tm)
2349                                 if (tm.team != COLOR_SPECTATOR)
2350                                 if (tm.team_size == ts_max)
2351                                         s = strcat(s, " Press ^3", getcommandkey("team menu", "menu_showteamselect"), blinkcolor, " to adjust");
2352
2353                                 o = Sbar_DrawNoteLine(o, s);
2354                         }
2355                 }
2356         }
2357
2358         Sbar_UpdatePlayerTeams();
2359
2360         if (intermission == 2) // map voting screen
2361         {
2362                 if(sb_showscores) {
2363                         Sbar_DrawScoreboard();
2364                         Sbar_Score(16);
2365                 }
2366                 else if(sb_showaccuracy) {
2367                         Sbar_DrawAccuracyStats();
2368                         Sbar_Score(16);
2369                 }
2370                 else
2371                         Sbar_FinaleOverlay();
2372         }
2373         else if (sb_showscores_force || getstati(STAT_HEALTH) <= 0 || intermission == 1)
2374         {
2375                 if(sb_showaccuracy)
2376                         Sbar_DrawAccuracyStats();
2377                 else
2378                         Sbar_DrawScoreboard();
2379                 Sbar_Score(16);
2380         }
2381         else
2382         {
2383                 if (sb_showscores)
2384                         Sbar_DrawScoreboard();
2385                 else if(sb_showaccuracy)
2386                         Sbar_DrawAccuracyStats();
2387
2388                 float armor, health;
2389                 armor = getstati(STAT_ARMOR);
2390                 health = getstati(STAT_HEALTH);
2391
2392                 stat_items = getstati(STAT_ITEMS);
2393                 stat_weapons = getstati(STAT_WEAPONS);
2394
2395                 fade = 3.2 - 2 * (time - weapontime);
2396                 fade = bound(0.7, fade, 1);
2397
2398                 vector bg_size; // hud background size
2399                 bg_size_x = 800;
2400                 if (vid_conwidth > 800) // if conwidth > 800, resize the background image
2401                         bg_size_x = vid_conwidth;
2402                 bg_size_y = 58;
2403                 bg_size_z = 0;
2404
2405                 vector bgoffset; // hud background offset
2406                 bgoffset_x = 0;
2407                 bgoffset_y = 0;
2408                 bgoffset_z = 0;
2409                 if (vid_conwidth < 800) // if conwidth < 800 we need to offset the background image to keep it centered, as it will be scaled up
2410                         bgoffset_x = (vid_conwidth - 800) / 2;
2411
2412                 if (cvar("viewsize") <= 100) {
2413                         if (teamplay)
2414                                 drawpic(bottomleft - '0 58 0' + bgoffset, "gfx/hud/sbar", bg_size, GetTeamRGB(myteam) * sbar_color_bg_team, sbar_alpha_bg, DRAWFLAG_NORMAL); // hud color = myteam color
2415                         else {
2416                                 // allow for custom HUD colors in non-teamgames
2417                                 color_x = cvar("sbar_color_bg_r");
2418                                 color_y = cvar("sbar_color_bg_g");
2419                                 color_z = cvar("sbar_color_bg_b");
2420
2421                                 drawpic(bottomleft - '0 58 0' + bgoffset, "gfx/hud/sbar", bg_size, color, sbar_alpha_bg, DRAWFLAG_NORMAL);
2422                         }
2423                 }
2424
2425                 if(sbar_hudselector == 2) // combined health and armor display
2426                 {
2427                         vector v;
2428                         v = healtharmor_maxdamage(health, armor, armorblockpercent);
2429
2430                         vector num_pos;
2431                         num_pos = bottom - element_offset - '0 22 0' + '-96 0 0';
2432
2433                         x = floor(v_x + 1);
2434
2435                         if(v_z) // fully armored
2436                         {
2437                                 // here, armorideal > armor
2438                                 drawpic(num_pos + '78 -4.5 0', "gfx/hud/sb_health", '32 32 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2439                                 drawpic(num_pos + '108 -4.5 0', "gfx/hud/sb_armor", '20 20 0', '1 1 1', sbar_alpha_fg * armor / v_y, DRAWFLAG_NORMAL);
2440                         }
2441                         else
2442                         {
2443                                 drawpic(num_pos + '108 -4.5 0', "gfx/hud/sb_health", '20 20 0', '1 1 1', sbar_alpha_fg * v_y / armor, DRAWFLAG_NORMAL);
2444                                 drawpic(num_pos + '78 -4.5 0', "gfx/hud/sb_armor", '32 32 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2445                         }
2446                         Sbar_DrawXNum_Colored(num_pos, x, 24, sbar_alpha_fg); // draw the combined health and armor
2447                 }
2448
2449                 else
2450                 {
2451                         vector health_pos, armor_pos;
2452
2453                         if (sbar_hudselector == 0) { // old style layout with armor left of health
2454                                 health_pos = bottom - element_offset - '0 22 0' + '14 0 0';
2455                                 armor_pos = bottom - element_offset - '0 22 0' + '-96 0 0';
2456                         }
2457                         else {
2458                                 health_pos = bottom - element_offset - '0 22 0' + '-96 0 0';
2459                                 armor_pos = bottom - element_offset - '0 22 0' + '14 0 0';
2460                         }
2461
2462                         // armor
2463                         x = armor;
2464                         if (x > 0)
2465                         {
2466                                 if (x > 45) {
2467                                         drawpic(armor_pos + '78 -4.5 0', "gfx/hud/sb_armor", '32 32 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2468                                         Sbar_DrawXNum_Colored(armor_pos, x, 24, sbar_alpha_fg);
2469                                 }
2470                                 else {
2471                                         drawpic(armor_pos + '78 -4.5 0', "gfx/hud/sb_armor", '32 32 0', '1 1 1', (x+10)/55 * sbar_alpha_fg, DRAWFLAG_NORMAL);
2472                                         Sbar_DrawXNum_Colored(armor_pos, x, 24, (x+10)/55 * sbar_alpha_fg);
2473                                 }
2474                         }
2475
2476                         // health
2477                         x = health;
2478                         drawpic(health_pos + '78 -4.5 0', "gfx/hud/sb_health", '32 32 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2479                         Sbar_DrawXNum_Colored(health_pos, x, 24, sbar_alpha_fg);
2480                 }
2481
2482                 // weapon icons
2483                 x = 1.0;
2484                 Sbar_DrawWeapon_Clear();
2485                 for(i = 1; i <= 24; ++i)
2486                 {
2487                         if(weaponimpulse[i-1] >= 0)
2488                         if(stat_weapons & x)
2489                         {
2490                                 Sbar_DrawWeapon(i-1, fade, (i == activeweapon), i);
2491                         }
2492                         x *= 2;
2493                 }
2494
2495                 // ammo
2496                 float a; // i will be the ammo type (already declared), a will contain how much ammo there is of type i
2497
2498                 for (i = 0; i < 4; ++i) {
2499                         a = getstati(GetAmmoStat(i)); // how much ammo do we have of type i?
2500
2501                         if(sbar_currentammo || vid_conwidth < 800) // force showing current ammo only with conwidths < 800
2502                         {
2503                                 if (stat_items & GetAmmoItemCode(i))
2504                                 {
2505                                         if (vid_conwidth < 800)
2506                                                 pos_x = 105;
2507                                         else
2508                                                 pos_x = 170;
2509                                         pos_y = -40;
2510
2511                                         drawpic(bottomleft + pos + '0 1.5 0', "gfx/hud/sb_ammobg", '107 29 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2512                                         drawpic(bottomleft + pos + '76 3 0', GetAmmoPicture(i), '24 24 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2513                                         if(a < 10)
2514                                                 Sbar_DrawXNum(bottomleft + pos + '5 5 0', a, 3, 24, '0.7 0 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2515                                         else
2516                                                 Sbar_DrawXNum(bottomleft + pos + '5 5 0', a, 3, 24, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2517                                 }
2518                         }
2519                         else
2520                         {
2521                                 if (a > 0) {
2522                                         switch (i) {
2523                                                 case 0: pos_x = 114; pos_y = -48; break; // shells
2524                                                 case 1: pos_x = 114; pos_y = -26; break; // bullets
2525                                                 case 2: pos_x = 200; pos_y = -48; break; // rockets
2526                                                 case 3: pos_x = 200; pos_y = -26; break; // cells
2527                                         }
2528
2529                                         if (stat_items & GetAmmoItemCode(i))
2530                                                 drawpic(bottomleft + pos + '0 1.5 0', "gfx/hud/sb_ammobg", '80 22 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2531                                         drawpic(bottomleft + pos + '56 3 0', GetAmmoPicture(i), '18 18 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2532                                         if (a < 10) {
2533                                                 if(stat_items & GetAmmoItemCode(i))
2534                                                         Sbar_DrawXNum(bottomleft + pos + '6 4.5 0', a, 3, 16, '0.7 0 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2535                                                 else
2536                                                         Sbar_DrawXNum(bottomleft + pos + '6 4.5 0', a, 3, 16, '0.7 0 0', 0, 0, sbar_alpha_fg * 0.7, DRAWFLAG_NORMAL);
2537                                         } else {
2538                                                 if(stat_items & GetAmmoItemCode(i))
2539                                                         Sbar_DrawXNum(bottomleft + pos + '6 4.5 0', a, 3, 16, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2540                                                 else
2541                                                         Sbar_DrawXNum(bottomleft + pos + '6 4.5 0', a, 3, 16, '0.7 0.7 0.7', 0, 0, sbar_alpha_fg * 0.7, DRAWFLAG_NORMAL);
2542                                         }
2543                                 }
2544                         }
2545                 }
2546
2547                 // fuel ammo
2548                 a = getstati(GetAmmoStat(4)); // how much fuel do we have?
2549
2550                 if (a > 0) { // if we have fuel, draw the amount
2551                         float invincibility_time, dt;
2552                         invincibility_time = getstatf(STAT_INVINCIBLE_FINISHED);
2553                         dt = invincibility_time - time;
2554                         if (dt > 0) { // if the invincibility timer is active, draw fuel ammo elsewhere
2555                                 pos_x = bottom_x + 140;
2556                                 pos_y = bottom_y - 72;
2557                         }
2558                         else { // if the invincibility timer is inactive, draw the fuel ammo there (it's rare to have invincibility + fuel anyway)
2559                                 pos_x = bottom_x + 140;
2560                                 pos_y = bottom_y - 22;
2561                         }
2562                         drawpic(pos - '0 2 0' + '52 0 0', GetAmmoPicture(4), '20 20 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL);
2563                         if (a > 10)
2564                                 Sbar_DrawXNum(pos, a, 3, 16, '1 1 1', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2565                         else
2566                                 Sbar_DrawXNum(pos, a, 3, 16, '0.7 0 0', 0, 0, sbar_alpha_fg, DRAWFLAG_NORMAL);
2567                 }
2568
2569                 // draw scores and timer
2570                 Sbar_Score(16);
2571
2572                 //show strength/invincibility ICON and timer:
2573                 CSQC_Strength_Timer();
2574
2575                 if(gametype == GAME_KEYHUNT)
2576                 {
2577                         CSQC_kh_hud();
2578                 } else if(gametype == GAME_CTF)
2579                 {
2580                         CSQC_ctf_hud();
2581                 } else if(gametype == GAME_NEXBALL)
2582                 {
2583                         CSQC_nb_hud();
2584                 }
2585         }
2586 }
2587
2588 // CTF HUD
2589 void CSQC_ctf_hud(void)
2590 {
2591         vector bottomleft, redflag_pos, blueflag_pos;
2592         bottomleft_x = 0;
2593         bottomleft_y = vid_conheight;
2594         bottomleft_z = 0;
2595
2596         float redflag, blueflag;
2597         float stat_items;
2598
2599         stat_items = getstati(STAT_ITEMS);
2600         redflag = (stat_items/IT_RED_FLAG_TAKEN) & 3;
2601         blueflag = (stat_items/IT_BLUE_FLAG_TAKEN) & 3;
2602
2603         if (myteam == COLOR_TEAM1) { // always draw own flag on left
2604                 redflag_pos = bottomleft - element_offset - '-4 36 0';
2605                 blueflag_pos = bottomleft - element_offset - '-68 36 0';
2606         }
2607
2608         else {
2609                 blueflag_pos = bottomleft - element_offset - '-4 36 0';
2610                 redflag_pos = bottomleft - element_offset - '-68 36 0';
2611         }
2612
2613         switch(redflag)
2614         {
2615         case 1: drawpic(redflag_pos, "gfx/hud/sb_flag_red_taken", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2616         case 2: drawpic(redflag_pos, "gfx/hud/sb_flag_red_lost", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2617         case 3: drawpic(redflag_pos, "gfx/hud/sb_flag_red_carrying", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2618         default:
2619                 if(stat_items & IT_CTF_SHIELDED)
2620                         if(myteam == COLOR_TEAM2)
2621                                 drawpic(redflag_pos, "gfx/hud/sb_flag_red_shielded", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2622         }
2623
2624         switch(blueflag)
2625         {
2626         case 1: drawpic(blueflag_pos, "gfx/hud/sb_flag_blue_taken", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2627         case 2: drawpic(blueflag_pos, "gfx/hud/sb_flag_blue_lost", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2628         case 3: drawpic(blueflag_pos, "gfx/hud/sb_flag_blue_carrying", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2629         default:
2630                 if(stat_items & IT_CTF_SHIELDED)
2631                         if(myteam == COLOR_TEAM1)
2632                                 drawpic(blueflag_pos, "gfx/hud/sb_flag_blue_shielded", '48 48 0', '1 1 1', sbar_alpha_fg, DRAWFLAG_NORMAL); break;
2633         }
2634 }
2635
2636 // Keyhunt HUD
2637 void CSQC_kh_hud(void)
2638 {
2639         float kh_keys, kh_keys_status, kh_teams_set;
2640         vector red_pos, blue_pos, yellow_pos, pink_pos, kh_size;
2641
2642         vector bottomleft;
2643         bottomleft_x = 0;
2644         bottomleft_y = vid_conheight;
2645         bottomleft_z = 0;
2646
2647         red_pos_x = 6;
2648         red_pos_y = vid_conheight - 35 - 6;
2649         red_pos_z = 0;
2650
2651         blue_pos_x = 6 + (24 * 1);
2652         blue_pos_y = vid_conheight - 35 - 6;
2653         blue_pos_z = 0;
2654
2655         yellow_pos_x = 6 + (24 * 2);
2656         yellow_pos_y = vid_conheight - 35 - 6;
2657         yellow_pos_z = 0;
2658
2659         pink_pos_x = 6 + (24 * 3);
2660         pink_pos_y = vid_conheight - 35 - 6;
2661         pink_pos_z = 0;
2662
2663         kh_keys = getstati(STAT_KH_KEYS);
2664         kh_keys_status = kh_keys / 256;
2665         kh_teams_set = cvar("_teams_available");  // set in keyhunt.qc
2666
2667         kh_size = '22 35 0';
2668
2669         if (kh_keys_status & 1)  // red
2670                 drawpic (red_pos, "gfx/hud/sb_kh_red", kh_size, '1 1 1', 0.3, DRAWFLAG_NORMAL);  // show 30% alpha key
2671         if (kh_keys & 1)
2672                 drawpic (red_pos, "gfx/hud/sb_kh_red", kh_size, '1 1 1', 1.0, DRAWFLAG_NORMAL);  // show solid key 100% alpha
2673
2674         if (kh_keys_status & 2)  // blue
2675                 drawpic (blue_pos, "gfx/hud/sb_kh_blue", kh_size, '1 1 1', 0.3, DRAWFLAG_NORMAL);
2676         if (kh_keys & 2)
2677                 drawpic (blue_pos, "gfx/hud/sb_kh_blue", kh_size, '1 1 1', 1.0, DRAWFLAG_NORMAL);
2678
2679         if (kh_teams_set & 4)  // yellow
2680         {
2681                 if (kh_keys_status & 4)
2682                         drawpic (yellow_pos, "gfx/hud/sb_kh_yellow", kh_size, '1 1 1', 0.3, DRAWFLAG_NORMAL);
2683                 if (kh_keys & 4)
2684                         drawpic (yellow_pos, "gfx/hud/sb_kh_yellow", kh_size, '1 1 1', 1.0, DRAWFLAG_NORMAL);
2685         }
2686
2687         if (kh_teams_set & 8)  // pink
2688         {
2689                 if (kh_keys_status & 8)
2690                         drawpic (pink_pos, "gfx/hud/sb_kh_pink", kh_size, '1 1 1', 0.3, DRAWFLAG_NORMAL);
2691                 if (kh_keys & 8)
2692                         drawpic (pink_pos, "gfx/hud/sb_kh_pink", kh_size, '1 1 1', 1.0, DRAWFLAG_NORMAL);
2693         }
2694 }
2695
2696 //Nexball HUD
2697 #define NBPB_SIZE '96 38 0'
2698 #define NBPB_BT 2                   //thickness
2699 #define NBPB_BRGB '1 1 1'
2700 #define NBPB_BALPH 1                //alpha
2701 #define NBPB_BFLAG DRAWFLAG_NORMAL
2702 #define NBPB_IALPH 0.4
2703 #define NBPB_IFLAG DRAWFLAG_NORMAL
2704 #define NBPB_IRGB '0.7 0.1 0'
2705
2706 void CSQC_nb_hud(void)
2707 {
2708         float stat_items, nb_pb_starttime, dt, p;
2709         vector pos;
2710
2711         stat_items = getstati(STAT_ITEMS);
2712         nb_pb_starttime = getstatf(STAT_NB_METERSTART);
2713
2714         pos_x = 4;
2715         pos_y = vid_conheight - 42;
2716         pos_z = 0;
2717
2718         //Manage the progress bar if any
2719         if (nb_pb_starttime > 0)
2720         {
2721                 vector s;
2722                 dt = mod(time - nb_pb_starttime, nb_pb_period);
2723                 // one period of positive triangle
2724                 p = 2 * dt / nb_pb_period;
2725                 if (p > 1)
2726                         p = 2 - p;
2727
2728                 s = NBPB_SIZE;
2729                 //Draw the filling
2730                 drawfill(pos, p * s_x * '1 0 0' + s_y * '0 1 0', NBPB_IRGB, NBPB_IALPH, NBPB_IFLAG);
2731
2732                 //Draw the box
2733                 s = NBPB_SIZE;
2734                 drawline(NBPB_BT, pos    , pos + '1 0 0' * s_x, NBPB_BRGB, NBPB_BALPH, NBPB_BFLAG);
2735                 drawline(NBPB_BT, pos    , pos + '0 1 0' * s_y, NBPB_BRGB, NBPB_BALPH, NBPB_BFLAG);
2736                 drawline(NBPB_BT, pos + s, pos + '1 0 0' * s_x, NBPB_BRGB, NBPB_BALPH, NBPB_BFLAG);
2737                 drawline(NBPB_BT, pos + s, pos + '0 1 0' * s_y, NBPB_BRGB, NBPB_BALPH, NBPB_BFLAG);
2738         }
2739
2740         pos_x += 12; //horizontal margin to the picture
2741         pos_y += 2; //vertical margin to the picture
2742
2743         if (stat_items & IT_KEY1)
2744                 drawpic(pos, "gfx/hud/sb_nexball_carrying", '80 34 0', '1 1 1', 1, DRAWFLAG_NORMAL);
2745 }