1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/draw.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 void Scoreboard_Draw_Export(int fh)
21 // allow saving cvars that aesthetically change the panel into hud skin files
22 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
25 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
32 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
33 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
37 const int MAX_SBT_FIELDS = MAX_SCORE;
39 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
40 float sbt_field_size[MAX_SBT_FIELDS + 1];
41 string sbt_field_title[MAX_SBT_FIELDS + 1];
44 string autocvar_hud_fontsize;
45 string hud_fontsize_str;
50 float sbt_fg_alpha_self;
52 float sbt_highlight_alpha;
53 float sbt_highlight_alpha_self;
55 // provide basic panel cvars to old clients
56 // TODO remove them after a future release (0.8.2+)
57 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
58 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
59 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
60 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
61 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
62 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
63 noref string autocvar_hud_panel_scoreboard_bg_border = "";
64 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
66 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
67 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
68 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
69 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
70 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
71 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
72 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
73 bool autocvar_hud_panel_scoreboard_table_highlight = true;
74 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
75 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
76 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
77 float autocvar_hud_panel_scoreboard_namesize = 15;
78 float autocvar_hud_panel_scoreboard_team_size_position = 0;
80 bool autocvar_hud_panel_scoreboard_accuracy = true;
81 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
82 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
83 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
84 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
86 bool autocvar_hud_panel_scoreboard_dynamichud = false;
88 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
89 bool autocvar_hud_panel_scoreboard_others_showscore = true;
90 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
91 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
92 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
94 // mode 0: returns translated label
95 // mode 1: prints name and description of all the labels
96 string Label_getInfo(string label, int mode)
99 label = "bckills"; // first case in the switch
103 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
104 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
105 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
106 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
107 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
108 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
109 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
110 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
111 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
112 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
113 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
114 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
115 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
116 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
117 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
118 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
119 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
120 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
121 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
122 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
123 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
124 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
125 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
126 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
127 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
128 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
129 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
130 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
131 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
132 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
133 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
134 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
135 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
136 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
137 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
138 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
139 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
140 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
141 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
142 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
143 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
144 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
145 default: return label;
150 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
151 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
153 void Scoreboard_InitScores()
157 ps_primary = ps_secondary = NULL;
158 ts_primary = ts_secondary = -1;
159 FOREACH(Scores, true, {
160 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
161 if(f == SFL_SORT_PRIO_PRIMARY)
163 if(f == SFL_SORT_PRIO_SECONDARY)
166 if(ps_secondary == NULL)
167 ps_secondary = ps_primary;
169 for(i = 0; i < MAX_TEAMSCORE; ++i)
171 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
172 if(f == SFL_SORT_PRIO_PRIMARY)
174 if(f == SFL_SORT_PRIO_SECONDARY)
177 if(ts_secondary == -1)
178 ts_secondary = ts_primary;
180 Cmd_Scoreboard_SetFields(0);
184 void Scoreboard_UpdatePlayerTeams()
188 for(pl = players.sort_next; pl; pl = pl.sort_next)
191 int Team = entcs_GetScoreTeam(pl.sv_entnum);
192 if(SetTeam(pl, Team))
195 Scoreboard_UpdatePlayerPos(pl);
199 pl = players.sort_next;
204 print(strcat("PNUM: ", ftos(num), "\n"));
209 int Scoreboard_CompareScore(int vl, int vr, int f)
211 TC(int, vl); TC(int, vr); TC(int, f);
212 if(f & SFL_ZERO_IS_WORST)
214 if(vl == 0 && vr != 0)
216 if(vl != 0 && vr == 0)
220 return IS_INCREASING(f);
222 return IS_DECREASING(f);
226 float Scoreboard_ComparePlayerScores(entity left, entity right)
229 vl = entcs_GetTeam(left.sv_entnum);
230 vr = entcs_GetTeam(right.sv_entnum);
242 if(vl == NUM_SPECTATOR)
244 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
246 if(!left.gotscores && right.gotscores)
251 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
255 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
259 FOREACH(Scores, true, {
260 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
261 if (r >= 0) return r;
264 if (left.sv_entnum < right.sv_entnum)
270 void Scoreboard_UpdatePlayerPos(entity player)
273 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
275 SORT_SWAP(player, ent);
277 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
279 SORT_SWAP(ent, player);
283 float Scoreboard_CompareTeamScores(entity left, entity right)
287 if(left.team == NUM_SPECTATOR)
289 if(right.team == NUM_SPECTATOR)
292 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
296 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
300 for(i = 0; i < MAX_TEAMSCORE; ++i)
302 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
307 if (left.team < right.team)
313 void Scoreboard_UpdateTeamPos(entity Team)
316 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
318 SORT_SWAP(Team, ent);
320 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
322 SORT_SWAP(ent, Team);
326 void Cmd_Scoreboard_Help()
328 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
329 LOG_HELP(_("Usage:"));
330 LOG_HELP("^2scoreboard_columns_set ^3default");
331 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
332 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
333 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
334 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
335 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
336 LOG_HELP(_("The following field names are recognized (case insensitive):"));
342 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
343 "of game types, then a slash, to make the field show up only in these\n"
344 "or in all but these game types. You can also specify 'all' as a\n"
345 "field to show all fields available for the current game mode."));
348 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
349 "include/exclude ALL teams/noteams game modes."));
352 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
353 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
354 "right of the vertical bar aligned to the right."));
355 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
356 "other gamemodes except DM."));
359 // NOTE: adding a gametype with ? to not warn for an optional field
360 // make sure it's excluded in a previous exclusive rule, if any
361 // otherwise the previous exclusive rule warns anyway
362 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
363 #define SCOREBOARD_DEFAULT_COLUMNS \
364 "ping pl fps name |" \
365 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
366 " -teams,lms/deaths +ft,tdm/deaths" \
368 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
369 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
370 " +tdm,ft,dom,ons,as/teamkills"\
371 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
372 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
373 " +lms/lives +lms/rank" \
374 " +kh/kckills +kh/losses +kh/caps" \
375 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
376 " +as/objectives +nb/faults +nb/goals" \
377 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
378 " +dom/ticks +dom/takes" \
379 " -lms,rc,cts,inv,nb/score"
381 void Cmd_Scoreboard_SetFields(int argc)
386 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
390 return; // do nothing, we don't know gametype and scores yet
392 // sbt_fields uses strunzone on the titles!
393 if(!sbt_field_title[0])
394 for(i = 0; i < MAX_SBT_FIELDS; ++i)
395 sbt_field_title[i] = strzone("(null)");
397 // TODO: re enable with gametype dependant cvars?
398 if(argc < 3) // no arguments provided
399 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
402 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
406 if(argv(2) == "default" || argv(2) == "expand_default")
408 if(argv(2) == "expand_default")
409 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
410 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
412 else if(argv(2) == "all")
414 string s = "ping pl name |"; // scores without a label
415 FOREACH(Scores, true, {
417 if(it != ps_secondary)
418 if(scores_label(it) != "")
419 s = strcat(s, " ", scores_label(it));
421 if(ps_secondary != ps_primary)
422 s = strcat(s, " ", scores_label(ps_secondary));
423 s = strcat(s, " ", scores_label(ps_primary));
424 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
431 hud_fontsize = HUD_GetFontsize("hud_fontsize");
433 for(i = 1; i < argc - 1; ++i)
436 bool nocomplain = false;
437 if(substring(str, 0, 1) == "?")
440 str = substring(str, 1, strlen(str) - 1);
443 slash = strstrofs(str, "/", 0);
446 pattern = substring(str, 0, slash);
447 str = substring(str, slash + 1, strlen(str) - (slash + 1));
449 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
453 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
454 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
455 str = strtolower(str);
460 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
461 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
462 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
463 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
464 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
465 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
466 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
467 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
468 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
469 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
472 FOREACH(Scores, true, {
473 if (str == strtolower(scores_label(it))) {
475 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
485 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
489 sbt_field[sbt_num_fields] = j;
492 if(j == ps_secondary)
493 have_secondary = true;
498 if(sbt_num_fields >= MAX_SBT_FIELDS)
502 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
504 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
505 have_secondary = true;
506 if(ps_primary == ps_secondary)
507 have_secondary = true;
508 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
510 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
514 strunzone(sbt_field_title[sbt_num_fields]);
515 for(i = sbt_num_fields; i > 0; --i)
517 sbt_field_title[i] = sbt_field_title[i-1];
518 sbt_field_size[i] = sbt_field_size[i-1];
519 sbt_field[i] = sbt_field[i-1];
521 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
522 sbt_field[0] = SP_NAME;
524 LOG_INFO("fixed missing field 'name'");
528 strunzone(sbt_field_title[sbt_num_fields]);
529 for(i = sbt_num_fields; i > 1; --i)
531 sbt_field_title[i] = sbt_field_title[i-1];
532 sbt_field_size[i] = sbt_field_size[i-1];
533 sbt_field[i] = sbt_field[i-1];
535 sbt_field_title[1] = strzone("|");
536 sbt_field[1] = SP_SEPARATOR;
537 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
539 LOG_INFO("fixed missing field '|'");
542 else if(!have_separator)
544 strcpy(sbt_field_title[sbt_num_fields], "|");
545 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
546 sbt_field[sbt_num_fields] = SP_SEPARATOR;
548 LOG_INFO("fixed missing field '|'");
552 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
553 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
554 sbt_field[sbt_num_fields] = ps_secondary;
556 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
560 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
561 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
562 sbt_field[sbt_num_fields] = ps_primary;
564 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
568 sbt_field[sbt_num_fields] = SP_END;
572 vector sbt_field_rgb;
573 string sbt_field_icon0;
574 string sbt_field_icon1;
575 string sbt_field_icon2;
576 vector sbt_field_icon0_rgb;
577 vector sbt_field_icon1_rgb;
578 vector sbt_field_icon2_rgb;
579 string Scoreboard_GetName(entity pl)
581 if(ready_waiting && pl.ready)
583 sbt_field_icon0 = "gfx/scoreboard/player_ready";
587 int f = entcs_GetClientColors(pl.sv_entnum);
589 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
590 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
591 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
592 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
593 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
596 return entcs_GetName(pl.sv_entnum);
599 string Scoreboard_GetField(entity pl, PlayerScoreField field)
601 float tmp, num, denom;
604 sbt_field_rgb = '1 1 1';
605 sbt_field_icon0 = "";
606 sbt_field_icon1 = "";
607 sbt_field_icon2 = "";
608 sbt_field_icon0_rgb = '1 1 1';
609 sbt_field_icon1_rgb = '1 1 1';
610 sbt_field_icon2_rgb = '1 1 1';
615 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
616 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
620 tmp = max(0, min(220, f-80)) / 220;
621 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
627 f = pl.ping_packetloss;
628 tmp = pl.ping_movementloss;
629 if(f == 0 && tmp == 0)
631 str = ftos(ceil(f * 100));
633 str = strcat(str, "~", ftos(ceil(tmp * 100)));
634 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
635 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
639 return Scoreboard_GetName(pl);
642 f = pl.(scores(SP_KILLS));
643 f -= pl.(scores(SP_SUICIDES));
647 num = pl.(scores(SP_KILLS));
648 denom = pl.(scores(SP_DEATHS));
651 sbt_field_rgb = '0 1 0';
652 str = sprintf("%d", num);
653 } else if(num <= 0) {
654 sbt_field_rgb = '1 0 0';
655 str = sprintf("%.1f", num/denom);
657 str = sprintf("%.1f", num/denom);
661 f = pl.(scores(SP_KILLS));
662 f -= pl.(scores(SP_DEATHS));
665 sbt_field_rgb = '0 1 0';
667 sbt_field_rgb = '1 1 1';
669 sbt_field_rgb = '1 0 0';
675 float elo = pl.(scores(SP_ELO));
677 case -1: return "...";
678 case -2: return _("N/A");
679 default: return ftos(elo);
685 float fps = pl.(scores(SP_FPS));
688 sbt_field_rgb = '1 1 1';
689 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
691 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
692 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
696 case SP_DMG: case SP_DMGTAKEN:
697 return sprintf("%.1f k", pl.(scores(field)) / 1000);
699 default: case SP_SCORE:
700 tmp = pl.(scores(field));
701 f = scores_flags(field);
702 if(field == ps_primary)
703 sbt_field_rgb = '1 1 0';
704 else if(field == ps_secondary)
705 sbt_field_rgb = '0 1 1';
707 sbt_field_rgb = '1 1 1';
708 return ScoreString(f, tmp);
713 float sbt_fixcolumnwidth_len;
714 float sbt_fixcolumnwidth_iconlen;
715 float sbt_fixcolumnwidth_marginlen;
717 string Scoreboard_FixColumnWidth(int i, string str)
723 sbt_fixcolumnwidth_iconlen = 0;
725 if(sbt_field_icon0 != "")
727 sz = draw_getimagesize(sbt_field_icon0);
729 if(sbt_fixcolumnwidth_iconlen < f)
730 sbt_fixcolumnwidth_iconlen = f;
733 if(sbt_field_icon1 != "")
735 sz = draw_getimagesize(sbt_field_icon1);
737 if(sbt_fixcolumnwidth_iconlen < f)
738 sbt_fixcolumnwidth_iconlen = f;
741 if(sbt_field_icon2 != "")
743 sz = draw_getimagesize(sbt_field_icon2);
745 if(sbt_fixcolumnwidth_iconlen < f)
746 sbt_fixcolumnwidth_iconlen = f;
749 if(sbt_fixcolumnwidth_iconlen != 0)
751 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
752 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
755 sbt_fixcolumnwidth_marginlen = 0;
757 if(sbt_field[i] == SP_NAME) // name gets all remaining space
760 float remaining_space = 0;
761 for(j = 0; j < sbt_num_fields; ++j)
763 if (sbt_field[i] != SP_SEPARATOR)
764 remaining_space += sbt_field_size[j] + hud_fontsize.x;
765 sbt_field_size[i] = panel_size.x - remaining_space;
767 if (sbt_fixcolumnwidth_iconlen != 0)
768 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
769 float namesize = panel_size.x - remaining_space;
770 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
771 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
773 max_namesize = vid_conwidth - remaining_space;
776 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
778 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
779 if(sbt_field_size[i] < f)
780 sbt_field_size[i] = f;
785 void Scoreboard_initFieldSizes()
787 for(int i = 0; i < sbt_num_fields; ++i)
789 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
790 Scoreboard_FixColumnWidth(i, "");
794 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
797 vector column_dim = eY * panel_size.y;
799 column_dim.y -= 1.25 * hud_fontsize.y;
800 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
801 pos.x += hud_fontsize.x * 0.5;
802 for(i = 0; i < sbt_num_fields; ++i)
804 if(sbt_field[i] == SP_SEPARATOR)
806 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
809 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
810 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
811 pos.x += column_dim.x;
813 if(sbt_field[i] == SP_SEPARATOR)
815 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
816 for(i = sbt_num_fields - 1; i > 0; --i)
818 if(sbt_field[i] == SP_SEPARATOR)
821 pos.x -= sbt_field_size[i];
826 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
827 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
830 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
831 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
832 pos.x -= hud_fontsize.x;
837 pos.y += 1.25 * hud_fontsize.y;
841 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
843 TC(bool, is_self); TC(int, pl_number);
845 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
847 vector h_pos = item_pos;
848 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
849 // alternated rows highlighting
851 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
852 else if((sbt_highlight) && (!(pl_number % 2)))
853 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
855 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
857 vector pos = item_pos;
858 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
860 drawstring(pos+eX*(panel_size.x+.5*hud_fontsize.x)+eY, "\xE2\x97\x80", vec2(hud_fontsize.x, hud_fontsize.y), rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
862 pos.x += hud_fontsize.x * 0.5;
863 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
864 vector tmp = '0 0 0';
866 PlayerScoreField field;
867 for(i = 0; i < sbt_num_fields; ++i)
869 field = sbt_field[i];
870 if(field == SP_SEPARATOR)
873 if(is_spec && field != SP_NAME && field != SP_PING) {
874 pos.x += sbt_field_size[i] + hud_fontsize.x;
877 str = Scoreboard_GetField(pl, field);
878 str = Scoreboard_FixColumnWidth(i, str);
880 pos.x += sbt_field_size[i] + hud_fontsize.x;
882 if(field == SP_NAME) {
883 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
884 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
886 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
887 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
890 tmp.x = sbt_field_size[i] + hud_fontsize.x;
891 if(sbt_field_icon0 != "")
892 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
893 if(sbt_field_icon1 != "")
894 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
895 if(sbt_field_icon2 != "")
896 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
899 if(sbt_field[i] == SP_SEPARATOR)
901 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
902 for(i = sbt_num_fields-1; i > 0; --i)
904 field = sbt_field[i];
905 if(field == SP_SEPARATOR)
908 if(is_spec && field != SP_NAME && field != SP_PING) {
909 pos.x -= sbt_field_size[i] + hud_fontsize.x;
913 str = Scoreboard_GetField(pl, field);
914 str = Scoreboard_FixColumnWidth(i, str);
916 if(field == SP_NAME) {
917 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
918 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
920 tmp.x = sbt_fixcolumnwidth_len;
921 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
924 tmp.x = sbt_field_size[i];
925 if(sbt_field_icon0 != "")
926 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
927 if(sbt_field_icon1 != "")
928 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
929 if(sbt_field_icon2 != "")
930 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
931 pos.x -= sbt_field_size[i] + hud_fontsize.x;
936 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
939 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
942 vector h_pos = item_pos;
943 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
945 bool complete = (this_team == NUM_SPECTATOR);
948 if((sbt_highlight) && (!(pl_number % 2)))
949 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
951 vector pos = item_pos;
952 pos.x += hud_fontsize.x * 0.5;
953 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
955 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
957 width_limit -= stringwidth("...", false, hud_fontsize);
958 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
959 static float max_name_width = 0;
962 float min_fieldsize = 0;
963 float fieldpadding = hud_fontsize.x * 0.25;
964 if(this_team == NUM_SPECTATOR)
966 if(autocvar_hud_panel_scoreboard_spectators_showping)
967 min_fieldsize = stringwidth("999", false, hud_fontsize);
969 else if(autocvar_hud_panel_scoreboard_others_showscore)
970 min_fieldsize = stringwidth("99", false, hud_fontsize);
971 for(i = 0; pl; pl = pl.sort_next)
973 if(pl.team != this_team)
979 if(this_team == NUM_SPECTATOR)
981 if(autocvar_hud_panel_scoreboard_spectators_showping)
982 field = Scoreboard_GetField(pl, SP_PING);
984 else if(autocvar_hud_panel_scoreboard_others_showscore)
985 field = Scoreboard_GetField(pl, SP_SCORE);
987 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
988 float column_width = stringwidth(str, true, hud_fontsize);
989 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
991 if(column_width > max_name_width)
992 max_name_width = column_width;
993 column_width = max_name_width;
997 fieldsize = stringwidth(field, false, hud_fontsize);
998 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1001 if(pos.x + column_width > width_limit)
1006 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1011 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1012 pos.y += hud_fontsize.y * 1.25;
1016 vector name_pos = pos;
1017 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1018 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1019 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1022 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1023 h_size.y = hud_fontsize.y;
1024 vector field_pos = pos;
1025 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1026 field_pos.x += column_width - h_size.x;
1028 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1029 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1030 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1034 h_size.x = column_width + hud_fontsize.x * 0.25;
1035 h_size.y = hud_fontsize.y;
1036 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1038 pos.x += column_width;
1039 pos.x += hud_fontsize.x;
1041 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1044 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1046 int max_players = 999;
1047 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1049 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1052 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1053 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1054 height /= team_count;
1057 height -= panel_bg_padding * 2; // - padding
1058 max_players = floor(height / (hud_fontsize.y * 1.25));
1059 if(max_players <= 1)
1061 if(max_players == tm.team_size)
1066 entity me = playerslots[current_player];
1068 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1069 panel_size.y += panel_bg_padding * 2;
1072 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1073 if(panel.current_panel_bg != "0")
1074 end_pos.y += panel_bg_border * 2;
1076 if(panel_bg_padding)
1078 panel_pos += '1 1 0' * panel_bg_padding;
1079 panel_size -= '2 2 0' * panel_bg_padding;
1083 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1087 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1089 pos.y += 1.25 * hud_fontsize.y;
1092 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1094 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1097 // print header row and highlight columns
1098 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1100 // fill the table and draw the rows
1101 bool is_self = false;
1102 bool self_shown = false;
1104 for(pl = players.sort_next; pl; pl = pl.sort_next)
1106 if(pl.team != tm.team)
1108 if(i == max_players - 2 && pl != me)
1110 if(!self_shown && me.team == tm.team)
1112 Scoreboard_DrawItem(pos, rgb, me, true, i);
1114 pos.y += 1.25 * hud_fontsize.y;
1118 if(i >= max_players - 1)
1120 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1123 is_self = (pl.sv_entnum == current_player);
1124 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1127 pos.y += 1.25 * hud_fontsize.y;
1131 panel_size.x += panel_bg_padding * 2; // restore initial width
1135 bool Scoreboard_WouldDraw()
1137 if (MUTATOR_CALLHOOK(DrawScoreboard))
1139 else if (QuickMenu_IsOpened())
1141 else if (HUD_Radar_Clickable())
1143 else if (scoreboard_showscores)
1145 else if (intermission == 1)
1147 else if (intermission == 2)
1149 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1150 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1154 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1159 float average_accuracy;
1160 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1164 if (scoreboard_fade_alpha == 1)
1165 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1167 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1169 vector initial_pos = pos;
1171 WepSet weapons_stat = WepSet_GetFromStat();
1172 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1173 int disownedcnt = 0;
1175 FOREACH(Weapons, it != WEP_Null, {
1176 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1178 WepSet set = it.m_wepset;
1179 if(it.spawnflags & WEP_TYPE_OTHER)
1184 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1186 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1193 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1194 if (weapon_cnt <= 0) return pos;
1197 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1199 int columnns = ceil(weapon_cnt / rows);
1201 float weapon_height = 29;
1202 float height = hud_fontsize.y + weapon_height;
1204 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1205 pos.y += 1.25 * hud_fontsize.y;
1206 if(panel.current_panel_bg != "0")
1207 pos.y += panel_bg_border;
1210 panel_size.y = height * rows;
1211 panel_size.y += panel_bg_padding * 2;
1213 float panel_bg_alpha_save = panel_bg_alpha;
1214 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1216 panel_bg_alpha = panel_bg_alpha_save;
1218 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1219 if(panel.current_panel_bg != "0")
1220 end_pos.y += panel_bg_border * 2;
1222 if(panel_bg_padding)
1224 panel_pos += '1 1 0' * panel_bg_padding;
1225 panel_size -= '2 2 0' * panel_bg_padding;
1229 vector tmp = panel_size;
1231 float weapon_width = tmp.x / columnns / rows;
1234 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1238 // column highlighting
1239 for (int i = 0; i < columnns; ++i)
1241 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1244 for (int i = 0; i < rows; ++i)
1245 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1248 average_accuracy = 0;
1249 int weapons_with_stats = 0;
1251 pos.x += weapon_width / 2;
1253 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1256 Accuracy_LoadColors();
1258 float oldposx = pos.x;
1262 FOREACH(Weapons, it != WEP_Null, {
1263 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1265 WepSet set = it.m_wepset;
1266 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1268 if (it.spawnflags & WEP_TYPE_OTHER)
1272 if (weapon_stats >= 0)
1273 weapon_alpha = sbt_fg_alpha;
1275 weapon_alpha = 0.2 * sbt_fg_alpha;
1278 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1280 if (weapon_stats >= 0) {
1281 weapons_with_stats += 1;
1282 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1285 s = sprintf("%d%%", weapon_stats * 100);
1288 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1290 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1291 rgb = Accuracy_GetColor(weapon_stats);
1293 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1295 tmpos.x += weapon_width * rows;
1296 pos.x += weapon_width * rows;
1297 if (rows == 2 && column == columnns - 1) {
1305 if (weapons_with_stats)
1306 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1308 panel_size.x += panel_bg_padding * 2; // restore initial width
1310 if (scoreboard_acc_fade_alpha == 1)
1312 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1315 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1317 pos.x += hud_fontsize.x * 0.25;
1318 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1319 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1320 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1322 pos.y += hud_fontsize.y;
1327 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1328 float stat_secrets_found, stat_secrets_total;
1329 float stat_monsters_killed, stat_monsters_total;
1333 // get monster stats
1334 stat_monsters_killed = STAT(MONSTERS_KILLED);
1335 stat_monsters_total = STAT(MONSTERS_TOTAL);
1337 // get secrets stats
1338 stat_secrets_found = STAT(SECRETS_FOUND);
1339 stat_secrets_total = STAT(SECRETS_TOTAL);
1341 // get number of rows
1342 if(stat_secrets_total)
1344 if(stat_monsters_total)
1347 // if no rows, return
1351 // draw table header
1352 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1353 pos.y += 1.25 * hud_fontsize.y;
1354 if(panel.current_panel_bg != "0")
1355 pos.y += panel_bg_border;
1358 panel_size.y = hud_fontsize.y * rows;
1359 panel_size.y += panel_bg_padding * 2;
1362 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1363 if(panel.current_panel_bg != "0")
1364 end_pos.y += panel_bg_border * 2;
1366 if(panel_bg_padding)
1368 panel_pos += '1 1 0' * panel_bg_padding;
1369 panel_size -= '2 2 0' * panel_bg_padding;
1373 vector tmp = panel_size;
1376 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1379 if(stat_monsters_total)
1381 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1382 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1386 if(stat_secrets_total)
1388 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1389 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1392 panel_size.x += panel_bg_padding * 2; // restore initial width
1397 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1400 RANKINGS_RECEIVED_CNT = 0;
1401 for (i=RANKINGS_CNT-1; i>=0; --i)
1403 ++RANKINGS_RECEIVED_CNT;
1405 if (RANKINGS_RECEIVED_CNT == 0)
1408 vector hl_rgb = rgb + '0.5 0.5 0.5';
1410 pos.y += hud_fontsize.y;
1411 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1412 pos.y += 1.25 * hud_fontsize.y;
1413 if(panel.current_panel_bg != "0")
1414 pos.y += panel_bg_border;
1419 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1421 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1426 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1428 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1432 float ranksize = 3 * hud_fontsize.x;
1433 float timesize = 5 * hud_fontsize.x;
1434 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1435 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1436 columns = min(columns, RANKINGS_RECEIVED_CNT);
1438 // expand name column to fill the entire row
1439 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1440 namesize += available_space;
1441 columnsize.x += available_space;
1443 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1444 panel_size.y += panel_bg_padding * 2;
1448 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1449 if(panel.current_panel_bg != "0")
1450 end_pos.y += panel_bg_border * 2;
1452 if(panel_bg_padding)
1454 panel_pos += '1 1 0' * panel_bg_padding;
1455 panel_size -= '2 2 0' * panel_bg_padding;
1461 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1463 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1465 int column = 0, j = 0;
1466 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1467 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1474 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1475 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1476 else if(!((j + column) & 1) && sbt_highlight)
1477 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1479 str = count_ordinal(i+1);
1480 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1481 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1482 str = ColorTranslateRGB(grecordholder[i]);
1484 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1485 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1487 pos.y += 1.25 * hud_fontsize.y;
1489 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1493 pos.x += panel_size.x / columns;
1494 pos.y = panel_pos.y;
1497 strfree(zoned_name_self);
1499 panel_size.x += panel_bg_padding * 2; // restore initial width
1503 float scoreboard_time;
1504 bool have_weapon_stats;
1505 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1507 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1509 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1512 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1513 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1519 if (!have_weapon_stats)
1521 FOREACH(Weapons, it != WEP_Null, {
1522 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1523 if (weapon_stats >= 0)
1525 have_weapon_stats = true;
1529 if (!have_weapon_stats)
1536 void Scoreboard_Draw()
1538 if(!autocvar__hud_configure)
1540 if(!hud_draw_maximized) return;
1542 // frametime checks allow to toggle the scoreboard even when the game is paused
1543 if(scoreboard_active) {
1544 if (scoreboard_fade_alpha < 1)
1545 scoreboard_time = time;
1546 if(hud_configure_menu_open == 1)
1547 scoreboard_fade_alpha = 1;
1548 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1549 if (scoreboard_fadeinspeed && frametime)
1550 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1552 scoreboard_fade_alpha = 1;
1553 if(hud_fontsize_str != autocvar_hud_fontsize)
1555 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1556 Scoreboard_initFieldSizes();
1557 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1561 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1562 if (scoreboard_fadeoutspeed && frametime)
1563 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1565 scoreboard_fade_alpha = 0;
1568 if (!scoreboard_fade_alpha)
1570 scoreboard_acc_fade_alpha = 0;
1575 scoreboard_fade_alpha = 0;
1577 if (autocvar_hud_panel_scoreboard_dynamichud)
1580 HUD_Scale_Disable();
1582 if(scoreboard_fade_alpha <= 0)
1584 panel_fade_alpha *= scoreboard_fade_alpha;
1585 HUD_Panel_LoadCvars();
1587 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1588 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1589 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1590 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1591 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1592 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1594 // don't overlap with con_notify
1595 if(!autocvar__hud_configure)
1596 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1598 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1599 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1600 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1601 panel_size.x = fixed_scoreboard_width;
1603 Scoreboard_UpdatePlayerTeams();
1605 vector pos = panel_pos;
1610 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1612 // Begin of Game Info Section
1613 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1614 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1616 // Game Info: Game Type
1617 str = MapInfo_Type_ToText(gametype);
1618 draw_beginBoldFont();
1619 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_type_fontsize)), str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1622 // Game Info: Game Detail
1623 float tl = STAT(TIMELIMIT);
1624 float fl = STAT(FRAGLIMIT);
1625 float ll = STAT(LEADLIMIT);
1626 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1629 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1630 if(!gametype.m_hidelimits)
1635 str = strcat(str, "^7 / "); // delimiter
1638 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1639 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1640 (teamscores_label(ts_primary) == "fastest") ? "" :
1641 TranslateScoresLabel(teamscores_label(ts_primary))));
1645 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1646 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1647 (scores_label(ps_primary) == "fastest") ? "" :
1648 TranslateScoresLabel(scores_label(ps_primary))));
1653 if(tl > 0 || fl > 0)
1656 if (ll_and_fl && fl > 0)
1657 str = strcat(str, "^7 & ");
1659 str = strcat(str, "^7 / ");
1664 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1665 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1666 (teamscores_label(ts_primary) == "fastest") ? "" :
1667 TranslateScoresLabel(teamscores_label(ts_primary))));
1671 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1672 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1673 (scores_label(ps_primary) == "fastest") ? "" :
1674 TranslateScoresLabel(scores_label(ps_primary))));
1679 pos.y += sb_gameinfo_type_fontsize.y;
1680 drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
1682 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1683 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1684 // End of Game Info Section
1686 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1687 if(panel.current_panel_bg != "0")
1688 pos.y += panel_bg_border;
1690 // Draw the scoreboard
1691 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1694 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1698 vector panel_bg_color_save = panel_bg_color;
1699 vector team_score_baseoffset;
1700 vector team_size_baseoffset;
1701 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1703 // put team score to the left of scoreboard (and team size to the right)
1704 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1705 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1706 if(panel.current_panel_bg != "0")
1708 team_score_baseoffset.x -= panel_bg_border;
1709 team_size_baseoffset.x += panel_bg_border;
1714 // put team score to the right of scoreboard (and team size to the left)
1715 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1716 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1717 if(panel.current_panel_bg != "0")
1719 team_score_baseoffset.x += panel_bg_border;
1720 team_size_baseoffset.x -= panel_bg_border;
1724 int team_size_total = 0;
1725 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1727 // calculate team size total (sum of all team sizes)
1728 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1729 if(tm.team != NUM_SPECTATOR)
1730 team_size_total += tm.team_size;
1733 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1735 if(tm.team == NUM_SPECTATOR)
1740 draw_beginBoldFont();
1741 vector rgb = Team_ColorRGB(tm.team);
1742 str = ftos(tm.(teamscores(ts_primary)));
1743 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1745 // team score on the left (default)
1746 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1750 // team score on the right
1751 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1753 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1755 // team size (if set to show on the side)
1756 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1758 // calculate the starting position for the whole team size info string
1759 str = sprintf("%d/%d", tm.team_size, team_size_total);
1760 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1762 // team size on the left
1763 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1767 // team size on the right
1768 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1770 str = sprintf("%d", tm.team_size);
1771 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1772 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1773 str = sprintf("/%d", team_size_total);
1774 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1778 // secondary score, e.g. keyhunt
1779 if(ts_primary != ts_secondary)
1781 str = ftos(tm.(teamscores(ts_secondary)));
1782 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1785 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1790 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1793 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1796 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1797 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1798 else if(panel_bg_color_team > 0)
1799 panel_bg_color = rgb * panel_bg_color_team;
1801 panel_bg_color = rgb;
1802 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1804 panel_bg_color = panel_bg_color_save;
1808 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1809 if(tm.team != NUM_SPECTATOR)
1812 // display it anyway
1813 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1816 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1817 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1819 if(MUTATOR_CALLHOOK(ShowRankings)) {
1820 string ranktitle = M_ARGV(0, string);
1821 if(race_speedaward) {
1822 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1823 pos.y += 1.25 * hud_fontsize.y;
1825 if(race_speedaward_alltimebest) {
1826 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1827 pos.y += 1.25 * hud_fontsize.y;
1829 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1832 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1835 for(pl = players.sort_next; pl; pl = pl.sort_next)
1837 if(pl.team == NUM_SPECTATOR)
1839 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1840 if(tm.team == NUM_SPECTATOR)
1842 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1843 draw_beginBoldFont();
1844 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1846 pos.y += 1.25 * hud_fontsize.y;
1848 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1849 pos.y += 1.25 * hud_fontsize.y;
1856 // print information about respawn status
1857 float respawn_time = STAT(RESPAWN_TIME);
1861 if(respawn_time < 0)
1863 // a negative number means we are awaiting respawn, time value is still the same
1864 respawn_time *= -1; // remove mark now that we checked it
1866 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1867 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1869 str = sprintf(_("^1Respawning in ^3%s^1..."),
1870 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1871 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1873 count_seconds(ceil(respawn_time - time))
1877 else if(time < respawn_time)
1879 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1880 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1881 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1883 count_seconds(ceil(respawn_time - time))
1887 else if(time >= respawn_time)
1888 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1890 pos.y += 1.2 * hud_fontsize.y;
1891 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1894 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;