1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/draw.qh>
5 #include <client/main.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include "quickmenu.qh"
9 #include <common/ent_cs.qh>
10 #include <common/constants.qh>
11 #include <common/net_linked.qh>
12 #include <common/mapinfo.qh>
13 #include <common/minigames/cl_minigames.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 const int MAX_SBT_FIELDS = MAX_SCORE;
40 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
41 float sbt_field_size[MAX_SBT_FIELDS + 1];
42 string sbt_field_title[MAX_SBT_FIELDS + 1];
45 string autocvar_hud_fontsize;
46 string hud_fontsize_str;
51 float sbt_fg_alpha_self;
53 float sbt_highlight_alpha;
54 float sbt_highlight_alpha_self;
56 // provide basic panel cvars to old clients
57 // TODO remove them after a future release (0.8.2+)
58 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
59 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
60 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
61 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
62 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
63 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
64 noref string autocvar_hud_panel_scoreboard_bg_border = "";
65 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
67 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
68 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
69 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
70 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
71 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
72 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
73 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
74 bool autocvar_hud_panel_scoreboard_table_highlight = true;
75 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
76 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
77 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
78 float autocvar_hud_panel_scoreboard_namesize = 15;
79 float autocvar_hud_panel_scoreboard_team_size_position = 0;
81 bool autocvar_hud_panel_scoreboard_accuracy = true;
82 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
83 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
84 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
85 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
87 bool autocvar_hud_panel_scoreboard_dynamichud = false;
89 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
90 bool autocvar_hud_panel_scoreboard_others_showscore = true;
91 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
92 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
93 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
95 // mode 0: returns translated label
96 // mode 1: prints name and description of all the labels
97 string Label_getInfo(string label, int mode)
100 label = "bckills"; // first case in the switch
104 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
105 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
106 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")));
107 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
108 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
109 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
110 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
111 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
112 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
113 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
114 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
115 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
116 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
117 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
118 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
119 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
120 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
121 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
122 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
123 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
124 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
125 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
126 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
127 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
128 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
129 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
130 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
131 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")));
132 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
133 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
134 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
135 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
136 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
137 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
138 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
139 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
140 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
141 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
142 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
143 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
144 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
145 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
146 default: return label;
151 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
152 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
154 void Scoreboard_InitScores()
158 ps_primary = ps_secondary = NULL;
159 ts_primary = ts_secondary = -1;
160 FOREACH(Scores, true, {
161 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
162 if(f == SFL_SORT_PRIO_PRIMARY)
164 if(f == SFL_SORT_PRIO_SECONDARY)
167 if(ps_secondary == NULL)
168 ps_secondary = ps_primary;
170 for(i = 0; i < MAX_TEAMSCORE; ++i)
172 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
173 if(f == SFL_SORT_PRIO_PRIMARY)
175 if(f == SFL_SORT_PRIO_SECONDARY)
178 if(ts_secondary == -1)
179 ts_secondary = ts_primary;
181 Cmd_Scoreboard_SetFields(0);
185 void Scoreboard_UpdatePlayerTeams()
189 for(pl = players.sort_next; pl; pl = pl.sort_next)
192 int Team = entcs_GetScoreTeam(pl.sv_entnum);
193 if(SetTeam(pl, Team))
196 Scoreboard_UpdatePlayerPos(pl);
200 pl = players.sort_next;
205 print(strcat("PNUM: ", ftos(num), "\n"));
210 int Scoreboard_CompareScore(int vl, int vr, int f)
212 TC(int, vl); TC(int, vr); TC(int, f);
213 if(f & SFL_ZERO_IS_WORST)
215 if(vl == 0 && vr != 0)
217 if(vl != 0 && vr == 0)
221 return IS_INCREASING(f);
223 return IS_DECREASING(f);
227 float Scoreboard_ComparePlayerScores(entity left, entity right)
230 vl = entcs_GetTeam(left.sv_entnum);
231 vr = entcs_GetTeam(right.sv_entnum);
243 if(vl == NUM_SPECTATOR)
245 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
247 if(!left.gotscores && right.gotscores)
252 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
256 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
260 FOREACH(Scores, true, {
261 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
262 if (r >= 0) return r;
265 if (left.sv_entnum < right.sv_entnum)
271 void Scoreboard_UpdatePlayerPos(entity player)
274 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
276 SORT_SWAP(player, ent);
278 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
280 SORT_SWAP(ent, player);
284 float Scoreboard_CompareTeamScores(entity left, entity right)
288 if(left.team == NUM_SPECTATOR)
290 if(right.team == NUM_SPECTATOR)
293 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
297 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
301 for(i = 0; i < MAX_TEAMSCORE; ++i)
303 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
308 if (left.team < right.team)
314 void Scoreboard_UpdateTeamPos(entity Team)
317 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
319 SORT_SWAP(Team, ent);
321 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
323 SORT_SWAP(ent, Team);
327 void Cmd_Scoreboard_Help()
329 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
330 LOG_HELP(_("Usage:"));
331 LOG_HELP("^2scoreboard_columns_set ^3default");
332 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
333 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
334 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
335 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
336 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
337 LOG_HELP(_("The following field names are recognized (case insensitive):"));
343 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
344 "of game types, then a slash, to make the field show up only in these\n"
345 "or in all but these game types. You can also specify 'all' as a\n"
346 "field to show all fields available for the current game mode."));
349 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
350 "include/exclude ALL teams/noteams game modes."));
353 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
354 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
355 "right of the vertical bar aligned to the right."));
356 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
357 "other gamemodes except DM."));
360 // NOTE: adding a gametype with ? to not warn for an optional field
361 // make sure it's excluded in a previous exclusive rule, if any
362 // otherwise the previous exclusive rule warns anyway
363 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
364 #define SCOREBOARD_DEFAULT_COLUMNS \
365 "ping pl fps name |" \
366 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
367 " -teams,lms/deaths +ft,tdm/deaths" \
369 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
370 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
371 " +tdm,ft,dom,ons,as/teamkills"\
372 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
373 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
374 " +lms/lives +lms/rank" \
375 " +kh/kckills +kh/losses +kh/caps" \
376 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
377 " +as/objectives +nb/faults +nb/goals" \
378 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
379 " +dom/ticks +dom/takes" \
380 " -lms,rc,cts,inv,nb/score"
382 void Cmd_Scoreboard_SetFields(int argc)
387 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
391 return; // do nothing, we don't know gametype and scores yet
393 // sbt_fields uses strunzone on the titles!
394 if(!sbt_field_title[0])
395 for(i = 0; i < MAX_SBT_FIELDS; ++i)
396 sbt_field_title[i] = strzone("(null)");
398 // TODO: re enable with gametype dependant cvars?
399 if(argc < 3) // no arguments provided
400 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
403 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
407 if(argv(2) == "default" || argv(2) == "expand_default")
409 if(argv(2) == "expand_default")
410 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
411 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
413 else if(argv(2) == "all")
415 string s = "ping pl name |"; // scores without a label
416 FOREACH(Scores, true, {
418 if(it != ps_secondary)
419 if(scores_label(it) != "")
420 s = strcat(s, " ", scores_label(it));
422 if(ps_secondary != ps_primary)
423 s = strcat(s, " ", scores_label(ps_secondary));
424 s = strcat(s, " ", scores_label(ps_primary));
425 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
432 hud_fontsize = HUD_GetFontsize("hud_fontsize");
434 for(i = 1; i < argc - 1; ++i)
437 bool nocomplain = false;
438 if(substring(str, 0, 1) == "?")
441 str = substring(str, 1, strlen(str) - 1);
444 slash = strstrofs(str, "/", 0);
447 pattern = substring(str, 0, slash);
448 str = substring(str, slash + 1, strlen(str) - (slash + 1));
450 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
454 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
455 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
456 str = strtolower(str);
461 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
462 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
463 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
464 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
465 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
466 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
467 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
468 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
469 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
470 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
473 FOREACH(Scores, true, {
474 if (str == strtolower(scores_label(it))) {
476 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
486 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
490 sbt_field[sbt_num_fields] = j;
493 if(j == ps_secondary)
494 have_secondary = true;
499 if(sbt_num_fields >= MAX_SBT_FIELDS)
503 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
505 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
506 have_secondary = true;
507 if(ps_primary == ps_secondary)
508 have_secondary = true;
509 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
511 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
515 strunzone(sbt_field_title[sbt_num_fields]);
516 for(i = sbt_num_fields; i > 0; --i)
518 sbt_field_title[i] = sbt_field_title[i-1];
519 sbt_field_size[i] = sbt_field_size[i-1];
520 sbt_field[i] = sbt_field[i-1];
522 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
523 sbt_field[0] = SP_NAME;
525 LOG_INFO("fixed missing field 'name'");
529 strunzone(sbt_field_title[sbt_num_fields]);
530 for(i = sbt_num_fields; i > 1; --i)
532 sbt_field_title[i] = sbt_field_title[i-1];
533 sbt_field_size[i] = sbt_field_size[i-1];
534 sbt_field[i] = sbt_field[i-1];
536 sbt_field_title[1] = strzone("|");
537 sbt_field[1] = SP_SEPARATOR;
538 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
540 LOG_INFO("fixed missing field '|'");
543 else if(!have_separator)
545 strcpy(sbt_field_title[sbt_num_fields], "|");
546 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
547 sbt_field[sbt_num_fields] = SP_SEPARATOR;
549 LOG_INFO("fixed missing field '|'");
553 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_secondary;
557 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
561 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
562 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
563 sbt_field[sbt_num_fields] = ps_primary;
565 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
569 sbt_field[sbt_num_fields] = SP_END;
573 vector sbt_field_rgb;
574 string sbt_field_icon0;
575 string sbt_field_icon1;
576 string sbt_field_icon2;
577 vector sbt_field_icon0_rgb;
578 vector sbt_field_icon1_rgb;
579 vector sbt_field_icon2_rgb;
580 string Scoreboard_GetName(entity pl)
582 if(ready_waiting && pl.ready)
584 sbt_field_icon0 = "gfx/scoreboard/player_ready";
588 int f = entcs_GetClientColors(pl.sv_entnum);
590 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
591 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
592 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
593 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
594 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
597 return entcs_GetName(pl.sv_entnum);
600 string Scoreboard_GetField(entity pl, PlayerScoreField field)
602 float tmp, num, denom;
605 sbt_field_rgb = '1 1 1';
606 sbt_field_icon0 = "";
607 sbt_field_icon1 = "";
608 sbt_field_icon2 = "";
609 sbt_field_icon0_rgb = '1 1 1';
610 sbt_field_icon1_rgb = '1 1 1';
611 sbt_field_icon2_rgb = '1 1 1';
616 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
617 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
621 tmp = max(0, min(220, f-80)) / 220;
622 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
628 f = pl.ping_packetloss;
629 tmp = pl.ping_movementloss;
630 if(f == 0 && tmp == 0)
632 str = ftos(ceil(f * 100));
634 str = strcat(str, "~", ftos(ceil(tmp * 100)));
635 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
636 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
640 return Scoreboard_GetName(pl);
643 f = pl.(scores(SP_KILLS));
644 f -= pl.(scores(SP_SUICIDES));
648 num = pl.(scores(SP_KILLS));
649 denom = pl.(scores(SP_DEATHS));
652 sbt_field_rgb = '0 1 0';
653 str = sprintf("%d", num);
654 } else if(num <= 0) {
655 sbt_field_rgb = '1 0 0';
656 str = sprintf("%.1f", num/denom);
658 str = sprintf("%.1f", num/denom);
662 f = pl.(scores(SP_KILLS));
663 f -= pl.(scores(SP_DEATHS));
666 sbt_field_rgb = '0 1 0';
668 sbt_field_rgb = '1 1 1';
670 sbt_field_rgb = '1 0 0';
676 float elo = pl.(scores(SP_ELO));
678 case -1: return "...";
679 case -2: return _("N/A");
680 default: return ftos(elo);
686 float fps = pl.(scores(SP_FPS));
689 sbt_field_rgb = '1 1 1';
690 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
692 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
693 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
697 case SP_DMG: case SP_DMGTAKEN:
698 return sprintf("%.1f k", pl.(scores(field)) / 1000);
700 default: case SP_SCORE:
701 tmp = pl.(scores(field));
702 f = scores_flags(field);
703 if(field == ps_primary)
704 sbt_field_rgb = '1 1 0';
705 else if(field == ps_secondary)
706 sbt_field_rgb = '0 1 1';
708 sbt_field_rgb = '1 1 1';
709 return ScoreString(f, tmp);
714 float sbt_fixcolumnwidth_len;
715 float sbt_fixcolumnwidth_iconlen;
716 float sbt_fixcolumnwidth_marginlen;
718 string Scoreboard_FixColumnWidth(int i, string str)
724 sbt_fixcolumnwidth_iconlen = 0;
726 if(sbt_field_icon0 != "")
728 sz = draw_getimagesize(sbt_field_icon0);
730 if(sbt_fixcolumnwidth_iconlen < f)
731 sbt_fixcolumnwidth_iconlen = f;
734 if(sbt_field_icon1 != "")
736 sz = draw_getimagesize(sbt_field_icon1);
738 if(sbt_fixcolumnwidth_iconlen < f)
739 sbt_fixcolumnwidth_iconlen = f;
742 if(sbt_field_icon2 != "")
744 sz = draw_getimagesize(sbt_field_icon2);
746 if(sbt_fixcolumnwidth_iconlen < f)
747 sbt_fixcolumnwidth_iconlen = f;
750 if(sbt_fixcolumnwidth_iconlen != 0)
752 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
753 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
756 sbt_fixcolumnwidth_marginlen = 0;
758 if(sbt_field[i] == SP_NAME) // name gets all remaining space
761 float remaining_space = 0;
762 for(j = 0; j < sbt_num_fields; ++j)
764 if (sbt_field[i] != SP_SEPARATOR)
765 remaining_space += sbt_field_size[j] + hud_fontsize.x;
766 sbt_field_size[i] = panel_size.x - remaining_space;
768 if (sbt_fixcolumnwidth_iconlen != 0)
769 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
770 float namesize = panel_size.x - remaining_space;
771 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
772 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
774 max_namesize = vid_conwidth - remaining_space;
777 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
779 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
780 if(sbt_field_size[i] < f)
781 sbt_field_size[i] = f;
786 void Scoreboard_initFieldSizes()
788 for(int i = 0; i < sbt_num_fields; ++i)
790 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
791 Scoreboard_FixColumnWidth(i, "");
795 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
798 vector column_dim = eY * panel_size.y;
800 column_dim.y -= 1.25 * hud_fontsize.y;
801 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
802 pos.x += hud_fontsize.x * 0.5;
803 for(i = 0; i < sbt_num_fields; ++i)
805 if(sbt_field[i] == SP_SEPARATOR)
807 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
810 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
811 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
812 pos.x += column_dim.x;
814 if(sbt_field[i] == SP_SEPARATOR)
816 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
817 for(i = sbt_num_fields - 1; i > 0; --i)
819 if(sbt_field[i] == SP_SEPARATOR)
822 pos.x -= sbt_field_size[i];
827 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
828 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
831 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
832 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
833 pos.x -= hud_fontsize.x;
838 pos.y += 1.25 * hud_fontsize.y;
842 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
844 TC(bool, is_self); TC(int, pl_number);
846 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
848 vector h_pos = item_pos;
849 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
850 // alternated rows highlighting
852 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
853 else if((sbt_highlight) && (!(pl_number % 2)))
854 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
856 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
858 vector pos = item_pos;
859 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
861 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);
863 pos.x += hud_fontsize.x * 0.5;
864 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
865 vector tmp = '0 0 0';
867 PlayerScoreField field;
868 for(i = 0; i < sbt_num_fields; ++i)
870 field = sbt_field[i];
871 if(field == SP_SEPARATOR)
874 if(is_spec && field != SP_NAME && field != SP_PING) {
875 pos.x += sbt_field_size[i] + hud_fontsize.x;
878 str = Scoreboard_GetField(pl, field);
879 str = Scoreboard_FixColumnWidth(i, str);
881 pos.x += sbt_field_size[i] + hud_fontsize.x;
883 if(field == SP_NAME) {
884 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
885 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
888 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
891 tmp.x = sbt_field_size[i] + hud_fontsize.x;
892 if(sbt_field_icon0 != "")
893 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
894 if(sbt_field_icon1 != "")
895 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
896 if(sbt_field_icon2 != "")
897 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
900 if(sbt_field[i] == SP_SEPARATOR)
902 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
903 for(i = sbt_num_fields-1; i > 0; --i)
905 field = sbt_field[i];
906 if(field == SP_SEPARATOR)
909 if(is_spec && field != SP_NAME && field != SP_PING) {
910 pos.x -= sbt_field_size[i] + hud_fontsize.x;
914 str = Scoreboard_GetField(pl, field);
915 str = Scoreboard_FixColumnWidth(i, str);
917 if(field == SP_NAME) {
918 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
919 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
921 tmp.x = sbt_fixcolumnwidth_len;
922 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
925 tmp.x = sbt_field_size[i];
926 if(sbt_field_icon0 != "")
927 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 if(sbt_field_icon1 != "")
929 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
930 if(sbt_field_icon2 != "")
931 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
932 pos.x -= sbt_field_size[i] + hud_fontsize.x;
937 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
940 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
943 vector h_pos = item_pos;
944 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
946 bool complete = (this_team == NUM_SPECTATOR);
949 if((sbt_highlight) && (!(pl_number % 2)))
950 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
952 vector pos = item_pos;
953 pos.x += hud_fontsize.x * 0.5;
954 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
956 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
958 width_limit -= stringwidth("...", false, hud_fontsize);
959 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
960 static float max_name_width = 0;
963 float min_fieldsize = 0;
964 float fieldpadding = hud_fontsize.x * 0.25;
965 if(this_team == NUM_SPECTATOR)
967 if(autocvar_hud_panel_scoreboard_spectators_showping)
968 min_fieldsize = stringwidth("999", false, hud_fontsize);
970 else if(autocvar_hud_panel_scoreboard_others_showscore)
971 min_fieldsize = stringwidth("99", false, hud_fontsize);
972 for(i = 0; pl; pl = pl.sort_next)
974 if(pl.team != this_team)
980 if(this_team == NUM_SPECTATOR)
982 if(autocvar_hud_panel_scoreboard_spectators_showping)
983 field = Scoreboard_GetField(pl, SP_PING);
985 else if(autocvar_hud_panel_scoreboard_others_showscore)
986 field = Scoreboard_GetField(pl, SP_SCORE);
988 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
989 float column_width = stringwidth(str, true, hud_fontsize);
990 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
992 if(column_width > max_name_width)
993 max_name_width = column_width;
994 column_width = max_name_width;
998 fieldsize = stringwidth(field, false, hud_fontsize);
999 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1002 if(pos.x + column_width > width_limit)
1007 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1012 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1013 pos.y += hud_fontsize.y * 1.25;
1017 vector name_pos = pos;
1018 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1019 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1020 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1023 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1024 h_size.y = hud_fontsize.y;
1025 vector field_pos = pos;
1026 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1027 field_pos.x += column_width - h_size.x;
1029 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1030 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1031 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1035 h_size.x = column_width + hud_fontsize.x * 0.25;
1036 h_size.y = hud_fontsize.y;
1037 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1039 pos.x += column_width;
1040 pos.x += hud_fontsize.x;
1042 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1045 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1047 int max_players = 999;
1048 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1050 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1053 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1054 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1055 height /= team_count;
1058 height -= panel_bg_padding * 2; // - padding
1059 max_players = floor(height / (hud_fontsize.y * 1.25));
1060 if(max_players <= 1)
1062 if(max_players == tm.team_size)
1067 entity me = playerslots[current_player];
1069 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1070 panel_size.y += panel_bg_padding * 2;
1073 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1074 if(panel.current_panel_bg != "0")
1075 end_pos.y += panel_bg_border * 2;
1077 if(panel_bg_padding)
1079 panel_pos += '1 1 0' * panel_bg_padding;
1080 panel_size -= '2 2 0' * panel_bg_padding;
1084 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1088 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1090 pos.y += 1.25 * hud_fontsize.y;
1093 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1095 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1098 // print header row and highlight columns
1099 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1101 // fill the table and draw the rows
1102 bool is_self = false;
1103 bool self_shown = false;
1105 for(pl = players.sort_next; pl; pl = pl.sort_next)
1107 if(pl.team != tm.team)
1109 if(i == max_players - 2 && pl != me)
1111 if(!self_shown && me.team == tm.team)
1113 Scoreboard_DrawItem(pos, rgb, me, true, i);
1115 pos.y += 1.25 * hud_fontsize.y;
1119 if(i >= max_players - 1)
1121 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1124 is_self = (pl.sv_entnum == current_player);
1125 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1128 pos.y += 1.25 * hud_fontsize.y;
1132 panel_size.x += panel_bg_padding * 2; // restore initial width
1136 bool Scoreboard_WouldDraw()
1138 if (MUTATOR_CALLHOOK(DrawScoreboard))
1140 else if (QuickMenu_IsOpened())
1142 else if (HUD_Radar_Clickable())
1144 else if (scoreboard_showscores)
1146 else if (intermission == 1)
1148 else if (intermission == 2)
1150 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1151 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1155 else if (scoreboard_showscores_force)
1160 float average_accuracy;
1161 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1165 if (scoreboard_fade_alpha == 1)
1166 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1168 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1170 vector initial_pos = pos;
1172 WepSet weapons_stat = WepSet_GetFromStat();
1173 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1174 int disownedcnt = 0;
1176 FOREACH(Weapons, it != WEP_Null, {
1177 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1179 WepSet set = it.m_wepset;
1180 if(it.spawnflags & WEP_TYPE_OTHER)
1185 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1187 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1194 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1195 if (weapon_cnt <= 0) return pos;
1198 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1200 int columnns = ceil(weapon_cnt / rows);
1202 float weapon_height = 29;
1203 float height = hud_fontsize.y + weapon_height;
1205 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);
1206 pos.y += 1.25 * hud_fontsize.y;
1207 if(panel.current_panel_bg != "0")
1208 pos.y += panel_bg_border;
1211 panel_size.y = height * rows;
1212 panel_size.y += panel_bg_padding * 2;
1214 float panel_bg_alpha_save = panel_bg_alpha;
1215 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1217 panel_bg_alpha = panel_bg_alpha_save;
1219 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1220 if(panel.current_panel_bg != "0")
1221 end_pos.y += panel_bg_border * 2;
1223 if(panel_bg_padding)
1225 panel_pos += '1 1 0' * panel_bg_padding;
1226 panel_size -= '2 2 0' * panel_bg_padding;
1230 vector tmp = panel_size;
1232 float weapon_width = tmp.x / columnns / rows;
1235 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1239 // column highlighting
1240 for (int i = 0; i < columnns; ++i)
1242 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);
1245 for (int i = 0; i < rows; ++i)
1246 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1249 average_accuracy = 0;
1250 int weapons_with_stats = 0;
1252 pos.x += weapon_width / 2;
1254 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1257 Accuracy_LoadColors();
1259 float oldposx = pos.x;
1263 FOREACH(Weapons, it != WEP_Null, {
1264 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1266 WepSet set = it.m_wepset;
1267 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1269 if (it.spawnflags & WEP_TYPE_OTHER)
1273 if (weapon_stats >= 0)
1274 weapon_alpha = sbt_fg_alpha;
1276 weapon_alpha = 0.2 * sbt_fg_alpha;
1279 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1281 if (weapon_stats >= 0) {
1282 weapons_with_stats += 1;
1283 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1286 s = sprintf("%d%%", weapon_stats * 100);
1289 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1291 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1292 rgb = Accuracy_GetColor(weapon_stats);
1294 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1296 tmpos.x += weapon_width * rows;
1297 pos.x += weapon_width * rows;
1298 if (rows == 2 && column == columnns - 1) {
1306 if (weapons_with_stats)
1307 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1309 panel_size.x += panel_bg_padding * 2; // restore initial width
1311 if (scoreboard_acc_fade_alpha == 1)
1313 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1316 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1318 pos.x += hud_fontsize.x * 0.25;
1319 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1320 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1321 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1323 pos.y += hud_fontsize.y;
1328 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1329 float stat_secrets_found, stat_secrets_total;
1330 float stat_monsters_killed, stat_monsters_total;
1334 // get monster stats
1335 stat_monsters_killed = STAT(MONSTERS_KILLED);
1336 stat_monsters_total = STAT(MONSTERS_TOTAL);
1338 // get secrets stats
1339 stat_secrets_found = STAT(SECRETS_FOUND);
1340 stat_secrets_total = STAT(SECRETS_TOTAL);
1342 // get number of rows
1343 if(stat_secrets_total)
1345 if(stat_monsters_total)
1348 // if no rows, return
1352 // draw table header
1353 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1354 pos.y += 1.25 * hud_fontsize.y;
1355 if(panel.current_panel_bg != "0")
1356 pos.y += panel_bg_border;
1359 panel_size.y = hud_fontsize.y * rows;
1360 panel_size.y += panel_bg_padding * 2;
1363 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1364 if(panel.current_panel_bg != "0")
1365 end_pos.y += panel_bg_border * 2;
1367 if(panel_bg_padding)
1369 panel_pos += '1 1 0' * panel_bg_padding;
1370 panel_size -= '2 2 0' * panel_bg_padding;
1374 vector tmp = panel_size;
1377 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1380 if(stat_monsters_total)
1382 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1383 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1387 if(stat_secrets_total)
1389 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1390 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1393 panel_size.x += panel_bg_padding * 2; // restore initial width
1398 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1401 RANKINGS_RECEIVED_CNT = 0;
1402 for (i=RANKINGS_CNT-1; i>=0; --i)
1404 ++RANKINGS_RECEIVED_CNT;
1406 if (RANKINGS_RECEIVED_CNT == 0)
1409 vector hl_rgb = rgb + '0.5 0.5 0.5';
1411 pos.y += hud_fontsize.y;
1412 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1413 pos.y += 1.25 * hud_fontsize.y;
1414 if(panel.current_panel_bg != "0")
1415 pos.y += panel_bg_border;
1420 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1422 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1427 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1429 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1433 float ranksize = 3 * hud_fontsize.x;
1434 float timesize = 5 * hud_fontsize.x;
1435 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1436 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1437 columns = min(columns, RANKINGS_RECEIVED_CNT);
1439 // expand name column to fill the entire row
1440 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1441 namesize += available_space;
1442 columnsize.x += available_space;
1444 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1445 panel_size.y += panel_bg_padding * 2;
1449 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1450 if(panel.current_panel_bg != "0")
1451 end_pos.y += panel_bg_border * 2;
1453 if(panel_bg_padding)
1455 panel_pos += '1 1 0' * panel_bg_padding;
1456 panel_size -= '2 2 0' * panel_bg_padding;
1462 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1464 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1466 int column = 0, j = 0;
1467 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1468 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1475 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1476 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1477 else if(!((j + column) & 1) && sbt_highlight)
1478 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1480 str = count_ordinal(i+1);
1481 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1482 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1483 str = ColorTranslateRGB(grecordholder[i]);
1485 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1486 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1488 pos.y += 1.25 * hud_fontsize.y;
1490 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1494 pos.x += panel_size.x / columns;
1495 pos.y = panel_pos.y;
1498 strfree(zoned_name_self);
1500 panel_size.x += panel_bg_padding * 2; // restore initial width
1504 float scoreboard_time;
1505 bool have_weapon_stats;
1506 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1508 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1510 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1513 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1514 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1520 if (!have_weapon_stats)
1522 FOREACH(Weapons, it != WEP_Null, {
1523 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1524 if (weapon_stats >= 0)
1526 have_weapon_stats = true;
1530 if (!have_weapon_stats)
1537 void Scoreboard_Draw()
1539 if(!autocvar__hud_configure)
1541 if(!hud_draw_maximized) return;
1543 // frametime checks allow to toggle the scoreboard even when the game is paused
1544 if(scoreboard_active) {
1545 if (scoreboard_fade_alpha < 1)
1546 scoreboard_time = time;
1547 if(hud_configure_menu_open == 1)
1548 scoreboard_fade_alpha = 1;
1549 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1550 if (scoreboard_fadeinspeed && frametime)
1551 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1553 scoreboard_fade_alpha = 1;
1554 if(hud_fontsize_str != autocvar_hud_fontsize)
1556 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1557 Scoreboard_initFieldSizes();
1558 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1562 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1563 if (scoreboard_fadeoutspeed && frametime)
1564 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1566 scoreboard_fade_alpha = 0;
1569 if (!scoreboard_fade_alpha)
1571 scoreboard_acc_fade_alpha = 0;
1576 scoreboard_fade_alpha = 0;
1578 if (autocvar_hud_panel_scoreboard_dynamichud)
1581 HUD_Scale_Disable();
1583 if(scoreboard_fade_alpha <= 0)
1585 panel_fade_alpha *= scoreboard_fade_alpha;
1586 HUD_Panel_LoadCvars();
1588 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1589 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1590 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1591 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1592 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1593 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1595 // don't overlap with con_notify
1596 if(!autocvar__hud_configure)
1597 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1599 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1600 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1601 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1602 panel_size.x = fixed_scoreboard_width;
1604 Scoreboard_UpdatePlayerTeams();
1606 vector pos = panel_pos;
1611 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1613 // Begin of Game Info Section
1614 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1615 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1617 // Game Info: Game Type
1618 str = MapInfo_Type_ToText(gametype);
1619 draw_beginBoldFont();
1620 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);
1623 // Game Info: Game Detail
1624 float tl = STAT(TIMELIMIT);
1625 float fl = STAT(FRAGLIMIT);
1626 float ll = STAT(LEADLIMIT);
1627 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1630 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1631 if(!gametype.m_hidelimits)
1636 str = strcat(str, "^7 / "); // delimiter
1639 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1640 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1641 (teamscores_label(ts_primary) == "fastest") ? "" :
1642 TranslateScoresLabel(teamscores_label(ts_primary))));
1646 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1647 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1648 (scores_label(ps_primary) == "fastest") ? "" :
1649 TranslateScoresLabel(scores_label(ps_primary))));
1654 if(tl > 0 || fl > 0)
1657 if (ll_and_fl && fl > 0)
1658 str = strcat(str, "^7 & ");
1660 str = strcat(str, "^7 / ");
1665 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1666 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1667 (teamscores_label(ts_primary) == "fastest") ? "" :
1668 TranslateScoresLabel(teamscores_label(ts_primary))));
1672 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1673 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1674 (scores_label(ps_primary) == "fastest") ? "" :
1675 TranslateScoresLabel(scores_label(ps_primary))));
1680 pos.y += sb_gameinfo_type_fontsize.y;
1681 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
1683 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1684 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1685 // End of Game Info Section
1687 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1688 if(panel.current_panel_bg != "0")
1689 pos.y += panel_bg_border;
1691 // Draw the scoreboard
1692 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1695 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1699 vector panel_bg_color_save = panel_bg_color;
1700 vector team_score_baseoffset;
1701 vector team_size_baseoffset;
1702 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1704 // put team score to the left of scoreboard (and team size to the right)
1705 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1706 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1707 if(panel.current_panel_bg != "0")
1709 team_score_baseoffset.x -= panel_bg_border;
1710 team_size_baseoffset.x += panel_bg_border;
1715 // put team score to the right of scoreboard (and team size to the left)
1716 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1717 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1718 if(panel.current_panel_bg != "0")
1720 team_score_baseoffset.x += panel_bg_border;
1721 team_size_baseoffset.x -= panel_bg_border;
1725 int team_size_total = 0;
1726 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1728 // calculate team size total (sum of all team sizes)
1729 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1730 if(tm.team != NUM_SPECTATOR)
1731 team_size_total += tm.team_size;
1734 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1736 if(tm.team == NUM_SPECTATOR)
1741 draw_beginBoldFont();
1742 vector rgb = Team_ColorRGB(tm.team);
1743 str = ftos(tm.(teamscores(ts_primary)));
1744 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1746 // team score on the left (default)
1747 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1751 // team score on the right
1752 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1754 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1756 // team size (if set to show on the side)
1757 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1759 // calculate the starting position for the whole team size info string
1760 str = sprintf("%d/%d", tm.team_size, team_size_total);
1761 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1763 // team size on the left
1764 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1768 // team size on the right
1769 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1771 str = sprintf("%d", tm.team_size);
1772 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1773 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1774 str = sprintf("/%d", team_size_total);
1775 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1779 // secondary score, e.g. keyhunt
1780 if(ts_primary != ts_secondary)
1782 str = ftos(tm.(teamscores(ts_secondary)));
1783 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1786 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1791 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1794 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1797 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1798 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1799 else if(panel_bg_color_team > 0)
1800 panel_bg_color = rgb * panel_bg_color_team;
1802 panel_bg_color = rgb;
1803 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1805 panel_bg_color = panel_bg_color_save;
1809 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1810 if(tm.team != NUM_SPECTATOR)
1813 // display it anyway
1814 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1817 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1818 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1820 if(MUTATOR_CALLHOOK(ShowRankings)) {
1821 string ranktitle = M_ARGV(0, string);
1822 if(race_speedaward) {
1823 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);
1824 pos.y += 1.25 * hud_fontsize.y;
1826 if(race_speedaward_alltimebest) {
1827 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);
1828 pos.y += 1.25 * hud_fontsize.y;
1830 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1833 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1836 for(pl = players.sort_next; pl; pl = pl.sort_next)
1838 if(pl.team == NUM_SPECTATOR)
1840 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1841 if(tm.team == NUM_SPECTATOR)
1843 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1844 draw_beginBoldFont();
1845 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1847 pos.y += 1.25 * hud_fontsize.y;
1849 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1850 pos.y += 1.25 * hud_fontsize.y;
1857 // print information about respawn status
1858 float respawn_time = STAT(RESPAWN_TIME);
1862 if(respawn_time < 0)
1864 // a negative number means we are awaiting respawn, time value is still the same
1865 respawn_time *= -1; // remove mark now that we checked it
1867 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1868 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1870 str = sprintf(_("^1Respawning in ^3%s^1..."),
1871 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1872 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1874 count_seconds(ceil(respawn_time - time))
1878 else if(time < respawn_time)
1880 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1881 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1882 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1884 count_seconds(ceil(respawn_time - time))
1888 else if(time >= respawn_time)
1889 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1891 pos.y += 1.2 * hud_fontsize.y;
1892 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1895 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;