1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/main.qh>
6 #include <client/miscfunctions.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.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_ctf_leaderboard = true;
88 bool autocvar_hud_panel_scoreboard_dynamichud = false;
90 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
91 bool autocvar_hud_panel_scoreboard_others_showscore = true;
92 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
93 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
94 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
96 // mode 0: returns translated label
97 // mode 1: prints name and description of all the labels
98 string Label_getInfo(string label, int mode)
101 label = "bckills"; // first case in the switch
105 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
106 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
107 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")));
108 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
109 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
110 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
111 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
112 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
113 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
114 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
115 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
116 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
117 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
118 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
119 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
120 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
121 case "hunts": if (!mode) return CTX(_("SCO^hunts")); else LOG_HELP(strcat("^3", "hunts", " ^7", _("Number of successful hunter rounds (Survival)")));
122 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
123 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
124 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
125 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
126 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
127 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
128 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
129 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
130 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
131 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
132 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
133 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")));
134 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
135 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
136 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
137 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
138 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
139 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
140 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
141 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
142 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
143 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
144 case "survivals": if (!mode) return CTX(_("SCO^survivals")); else LOG_HELP(strcat("^3", "survivals", " ^7", _("Number of rounds survived (Survival)")));
145 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
146 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
147 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
148 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
149 default: return label;
154 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
155 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
157 void Scoreboard_InitScores()
161 ps_primary = ps_secondary = NULL;
162 ts_primary = ts_secondary = -1;
163 FOREACH(Scores, true, {
164 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
165 if(f == SFL_SORT_PRIO_PRIMARY)
167 if(f == SFL_SORT_PRIO_SECONDARY)
170 if(ps_secondary == NULL)
171 ps_secondary = ps_primary;
173 for(i = 0; i < MAX_TEAMSCORE; ++i)
175 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
181 if(ts_secondary == -1)
182 ts_secondary = ts_primary;
184 Cmd_Scoreboard_SetFields(0);
188 void Scoreboard_UpdatePlayerTeams()
192 for(pl = players.sort_next; pl; pl = pl.sort_next)
195 int Team = entcs_GetScoreTeam(pl.sv_entnum);
196 if(SetTeam(pl, Team))
199 Scoreboard_UpdatePlayerPos(pl);
203 pl = players.sort_next;
208 print(strcat("PNUM: ", ftos(num), "\n"));
213 int Scoreboard_CompareScore(int vl, int vr, int f)
215 TC(int, vl); TC(int, vr); TC(int, f);
216 if(f & SFL_ZERO_IS_WORST)
218 if(vl == 0 && vr != 0)
220 if(vl != 0 && vr == 0)
224 return IS_INCREASING(f);
226 return IS_DECREASING(f);
230 float Scoreboard_ComparePlayerScores(entity left, entity right)
233 vl = entcs_GetTeam(left.sv_entnum);
234 vr = entcs_GetTeam(right.sv_entnum);
246 if(vl == NUM_SPECTATOR)
248 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
250 if(!left.gotscores && right.gotscores)
255 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
259 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
263 FOREACH(Scores, true, {
264 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
265 if (r >= 0) return r;
268 if (left.sv_entnum < right.sv_entnum)
274 void Scoreboard_UpdatePlayerPos(entity player)
277 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
279 SORT_SWAP(player, ent);
281 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
283 SORT_SWAP(ent, player);
287 float Scoreboard_CompareTeamScores(entity left, entity right)
291 if(left.team == NUM_SPECTATOR)
293 if(right.team == NUM_SPECTATOR)
296 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
300 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
304 for(i = 0; i < MAX_TEAMSCORE; ++i)
306 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
311 if (left.team < right.team)
317 void Scoreboard_UpdateTeamPos(entity Team)
320 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
322 SORT_SWAP(Team, ent);
324 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
326 SORT_SWAP(ent, Team);
330 void Cmd_Scoreboard_Help()
332 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
333 LOG_HELP(_("Usage:"));
334 LOG_HELP("^2scoreboard_columns_set ^3default");
335 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
336 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
337 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
338 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
339 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
340 LOG_HELP(_("The following field names are recognized (case insensitive):"));
346 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
347 "of game types, then a slash, to make the field show up only in these\n"
348 "or in all but these game types. You can also specify 'all' as a\n"
349 "field to show all fields available for the current game mode."));
352 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
353 "include/exclude ALL teams/noteams game modes."));
356 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
357 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
358 "right of the vertical bar aligned to the right."));
359 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
360 "other gamemodes except DM."));
363 // NOTE: adding a gametype with ? to not warn for an optional field
364 // make sure it's excluded in a previous exclusive rule, if any
365 // otherwise the previous exclusive rule warns anyway
366 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
367 #define SCOREBOARD_DEFAULT_COLUMNS \
368 "ping pl fps name |" \
369 " -teams,rc,cts,inv,lms,surv/kills +ft,tdm/kills ?+rc,inv/kills" \
370 " -teams,lms,surv/deaths +ft,tdm/deaths" \
372 " -teams,lms,rc,cts,inv,ka,surv/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
373 " -cts,dm,tdm,ka,ft,surv/frags" /* tdm already has this in "score" */ \
374 " +tdm,ft,dom,ons,as/teamkills"\
375 " -rc,cts,nb,surv/dmg -rc,cts,nb,surv/dmgtaken" \
376 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
377 " +lms/lives +lms/rank" \
378 " +kh/kckills +kh/losses +kh/caps" \
379 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
380 " +as/objectives +nb/faults +nb/goals" \
381 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
382 " +dom/ticks +dom/takes" \
383 " +surv/survivals +surv/hunts" \
384 " -lms,rc,cts,inv,nb/score"
386 void Cmd_Scoreboard_SetFields(int argc)
391 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
395 return; // do nothing, we don't know gametype and scores yet
397 // sbt_fields uses strunzone on the titles!
398 if(!sbt_field_title[0])
399 for(i = 0; i < MAX_SBT_FIELDS; ++i)
400 sbt_field_title[i] = strzone("(null)");
402 // TODO: re enable with gametype dependant cvars?
403 if(argc < 3) // no arguments provided
404 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
407 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
411 if(argv(2) == "default" || argv(2) == "expand_default")
413 if(argv(2) == "expand_default")
414 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
415 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
417 else if(argv(2) == "all")
419 string s = "ping pl name |"; // scores without a label
420 FOREACH(Scores, true, {
422 if(it != ps_secondary)
423 if(scores_label(it) != "")
424 s = strcat(s, " ", scores_label(it));
426 if(ps_secondary != ps_primary)
427 s = strcat(s, " ", scores_label(ps_secondary));
428 s = strcat(s, " ", scores_label(ps_primary));
429 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
436 hud_fontsize = HUD_GetFontsize("hud_fontsize");
438 for(i = 1; i < argc - 1; ++i)
441 bool nocomplain = false;
442 if(substring(str, 0, 1) == "?")
445 str = substring(str, 1, strlen(str) - 1);
448 slash = strstrofs(str, "/", 0);
451 pattern = substring(str, 0, slash);
452 str = substring(str, slash + 1, strlen(str) - (slash + 1));
454 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
458 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
459 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
460 str = strtolower(str);
465 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
466 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
467 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
468 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
469 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
470 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
471 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
472 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
473 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
474 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
477 FOREACH(Scores, true, {
478 if (str == strtolower(scores_label(it))) {
480 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
490 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
494 sbt_field[sbt_num_fields] = j;
497 if(j == ps_secondary)
498 have_secondary = true;
503 if(sbt_num_fields >= MAX_SBT_FIELDS)
507 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
509 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
510 have_secondary = true;
511 if(ps_primary == ps_secondary)
512 have_secondary = true;
513 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
515 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
519 strunzone(sbt_field_title[sbt_num_fields]);
520 for(i = sbt_num_fields; i > 0; --i)
522 sbt_field_title[i] = sbt_field_title[i-1];
523 sbt_field_size[i] = sbt_field_size[i-1];
524 sbt_field[i] = sbt_field[i-1];
526 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
527 sbt_field[0] = SP_NAME;
529 LOG_INFO("fixed missing field 'name'");
533 strunzone(sbt_field_title[sbt_num_fields]);
534 for(i = sbt_num_fields; i > 1; --i)
536 sbt_field_title[i] = sbt_field_title[i-1];
537 sbt_field_size[i] = sbt_field_size[i-1];
538 sbt_field[i] = sbt_field[i-1];
540 sbt_field_title[1] = strzone("|");
541 sbt_field[1] = SP_SEPARATOR;
542 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
544 LOG_INFO("fixed missing field '|'");
547 else if(!have_separator)
549 strcpy(sbt_field_title[sbt_num_fields], "|");
550 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
551 sbt_field[sbt_num_fields] = SP_SEPARATOR;
553 LOG_INFO("fixed missing field '|'");
557 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
558 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
559 sbt_field[sbt_num_fields] = ps_secondary;
561 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
565 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
566 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
567 sbt_field[sbt_num_fields] = ps_primary;
569 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
573 sbt_field[sbt_num_fields] = SP_END;
577 vector sbt_field_rgb;
578 string sbt_field_icon0;
579 string sbt_field_icon1;
580 string sbt_field_icon2;
581 vector sbt_field_icon0_rgb;
582 vector sbt_field_icon1_rgb;
583 vector sbt_field_icon2_rgb;
584 string Scoreboard_GetName(entity pl)
586 if(ready_waiting && pl.ready)
588 sbt_field_icon0 = "gfx/scoreboard/player_ready";
592 int f = entcs_GetClientColors(pl.sv_entnum);
594 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
595 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
596 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
597 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
598 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
601 return entcs_GetName(pl.sv_entnum);
604 string Scoreboard_GetField(entity pl, PlayerScoreField field)
606 float tmp, num, denom;
609 sbt_field_rgb = '1 1 1';
610 sbt_field_icon0 = "";
611 sbt_field_icon1 = "";
612 sbt_field_icon2 = "";
613 sbt_field_icon0_rgb = '1 1 1';
614 sbt_field_icon1_rgb = '1 1 1';
615 sbt_field_icon2_rgb = '1 1 1';
620 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
621 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
625 tmp = max(0, min(220, f-80)) / 220;
626 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
632 f = pl.ping_packetloss;
633 tmp = pl.ping_movementloss;
634 if(f == 0 && tmp == 0)
636 str = ftos(ceil(f * 100));
638 str = strcat(str, "~", ftos(ceil(tmp * 100)));
639 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
640 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
644 return Scoreboard_GetName(pl);
647 f = pl.(scores(SP_KILLS));
648 f -= pl.(scores(SP_SUICIDES));
652 num = pl.(scores(SP_KILLS));
653 denom = pl.(scores(SP_DEATHS));
656 sbt_field_rgb = '0 1 0';
657 str = sprintf("%d", num);
658 } else if(num <= 0) {
659 sbt_field_rgb = '1 0 0';
660 str = sprintf("%.1f", num/denom);
662 str = sprintf("%.1f", num/denom);
666 f = pl.(scores(SP_KILLS));
667 f -= pl.(scores(SP_DEATHS));
670 sbt_field_rgb = '0 1 0';
672 sbt_field_rgb = '1 1 1';
674 sbt_field_rgb = '1 0 0';
680 float elo = pl.(scores(SP_ELO));
682 case -1: return "...";
683 case -2: return _("N/A");
684 default: return ftos(elo);
690 float fps = pl.(scores(SP_FPS));
693 sbt_field_rgb = '1 1 1';
694 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
696 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
697 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
701 case SP_DMG: case SP_DMGTAKEN:
702 return sprintf("%.1f k", pl.(scores(field)) / 1000);
704 default: case SP_SCORE:
705 tmp = pl.(scores(field));
706 f = scores_flags(field);
707 if(field == ps_primary)
708 sbt_field_rgb = '1 1 0';
709 else if(field == ps_secondary)
710 sbt_field_rgb = '0 1 1';
712 sbt_field_rgb = '1 1 1';
713 return ScoreString(f, tmp);
718 float sbt_fixcolumnwidth_len;
719 float sbt_fixcolumnwidth_iconlen;
720 float sbt_fixcolumnwidth_marginlen;
722 string Scoreboard_FixColumnWidth(int i, string str)
728 sbt_fixcolumnwidth_iconlen = 0;
730 if(sbt_field_icon0 != "")
732 sz = draw_getimagesize(sbt_field_icon0);
734 if(sbt_fixcolumnwidth_iconlen < f)
735 sbt_fixcolumnwidth_iconlen = f;
738 if(sbt_field_icon1 != "")
740 sz = draw_getimagesize(sbt_field_icon1);
742 if(sbt_fixcolumnwidth_iconlen < f)
743 sbt_fixcolumnwidth_iconlen = f;
746 if(sbt_field_icon2 != "")
748 sz = draw_getimagesize(sbt_field_icon2);
750 if(sbt_fixcolumnwidth_iconlen < f)
751 sbt_fixcolumnwidth_iconlen = f;
754 if(sbt_fixcolumnwidth_iconlen != 0)
756 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
757 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
760 sbt_fixcolumnwidth_marginlen = 0;
762 if(sbt_field[i] == SP_NAME) // name gets all remaining space
765 float remaining_space = 0;
766 for(j = 0; j < sbt_num_fields; ++j)
768 if (sbt_field[i] != SP_SEPARATOR)
769 remaining_space += sbt_field_size[j] + hud_fontsize.x;
770 sbt_field_size[i] = panel_size.x - remaining_space;
772 if (sbt_fixcolumnwidth_iconlen != 0)
773 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
774 float namesize = panel_size.x - remaining_space;
775 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
776 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
778 max_namesize = vid_conwidth - remaining_space;
781 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
783 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
784 if(sbt_field_size[i] < f)
785 sbt_field_size[i] = f;
790 void Scoreboard_initFieldSizes()
792 for(int i = 0; i < sbt_num_fields; ++i)
794 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
795 Scoreboard_FixColumnWidth(i, "");
799 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
802 vector column_dim = eY * panel_size.y;
804 column_dim.y -= 1.25 * hud_fontsize.y;
805 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
806 pos.x += hud_fontsize.x * 0.5;
807 for(i = 0; i < sbt_num_fields; ++i)
809 if(sbt_field[i] == SP_SEPARATOR)
811 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
814 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
815 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
816 pos.x += column_dim.x;
818 if(sbt_field[i] == SP_SEPARATOR)
820 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
821 for(i = sbt_num_fields - 1; i > 0; --i)
823 if(sbt_field[i] == SP_SEPARATOR)
826 pos.x -= sbt_field_size[i];
831 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
832 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
835 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
836 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
837 pos.x -= hud_fontsize.x;
842 pos.y += 1.25 * hud_fontsize.y;
846 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
848 TC(bool, is_self); TC(int, pl_number);
850 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
852 vector h_pos = item_pos;
853 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
854 // alternated rows highlighting
856 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
857 else if((sbt_highlight) && (!(pl_number % 2)))
858 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
860 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
862 vector pos = item_pos;
863 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
865 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);
867 pos.x += hud_fontsize.x * 0.5;
868 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
869 vector tmp = '0 0 0';
871 PlayerScoreField field;
872 for(i = 0; i < sbt_num_fields; ++i)
874 field = sbt_field[i];
875 if(field == SP_SEPARATOR)
878 if(is_spec && field != SP_NAME && field != SP_PING) {
879 pos.x += sbt_field_size[i] + hud_fontsize.x;
882 str = Scoreboard_GetField(pl, field);
883 str = Scoreboard_FixColumnWidth(i, str);
885 pos.x += sbt_field_size[i] + hud_fontsize.x;
887 if(field == SP_NAME) {
888 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
889 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
891 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
892 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
895 tmp.x = sbt_field_size[i] + hud_fontsize.x;
896 if(sbt_field_icon0 != "")
897 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
898 if(sbt_field_icon1 != "")
899 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
900 if(sbt_field_icon2 != "")
901 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
904 if(sbt_field[i] == SP_SEPARATOR)
906 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
907 for(i = sbt_num_fields-1; i > 0; --i)
909 field = sbt_field[i];
910 if(field == SP_SEPARATOR)
913 if(is_spec && field != SP_NAME && field != SP_PING) {
914 pos.x -= sbt_field_size[i] + hud_fontsize.x;
918 str = Scoreboard_GetField(pl, field);
919 str = Scoreboard_FixColumnWidth(i, str);
921 if(field == SP_NAME) {
922 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
923 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
925 tmp.x = sbt_fixcolumnwidth_len;
926 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
929 tmp.x = sbt_field_size[i];
930 if(sbt_field_icon0 != "")
931 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
932 if(sbt_field_icon1 != "")
933 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
934 if(sbt_field_icon2 != "")
935 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
936 pos.x -= sbt_field_size[i] + hud_fontsize.x;
941 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
944 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
947 vector h_pos = item_pos;
948 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
950 bool complete = (this_team == NUM_SPECTATOR);
953 if((sbt_highlight) && (!(pl_number % 2)))
954 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
956 vector pos = item_pos;
957 pos.x += hud_fontsize.x * 0.5;
958 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
960 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
962 width_limit -= stringwidth("...", false, hud_fontsize);
963 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
964 static float max_name_width = 0;
967 float min_fieldsize = 0;
968 float fieldpadding = hud_fontsize.x * 0.25;
969 if(this_team == NUM_SPECTATOR)
971 if(autocvar_hud_panel_scoreboard_spectators_showping)
972 min_fieldsize = stringwidth("999", false, hud_fontsize);
974 else if(autocvar_hud_panel_scoreboard_others_showscore)
975 min_fieldsize = stringwidth("99", false, hud_fontsize);
976 for(i = 0; pl; pl = pl.sort_next)
978 if(pl.team != this_team)
984 if(this_team == NUM_SPECTATOR)
986 if(autocvar_hud_panel_scoreboard_spectators_showping)
987 field = Scoreboard_GetField(pl, SP_PING);
989 else if(autocvar_hud_panel_scoreboard_others_showscore)
990 field = Scoreboard_GetField(pl, SP_SCORE);
992 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
993 float column_width = stringwidth(str, true, hud_fontsize);
994 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
996 if(column_width > max_name_width)
997 max_name_width = column_width;
998 column_width = max_name_width;
1002 fieldsize = stringwidth(field, false, hud_fontsize);
1003 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1006 if(pos.x + column_width > width_limit)
1011 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1016 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1017 pos.y += hud_fontsize.y * 1.25;
1021 vector name_pos = pos;
1022 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1023 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1024 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1027 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1028 h_size.y = hud_fontsize.y;
1029 vector field_pos = pos;
1030 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1031 field_pos.x += column_width - h_size.x;
1033 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1034 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1035 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1039 h_size.x = column_width + hud_fontsize.x * 0.25;
1040 h_size.y = hud_fontsize.y;
1041 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1043 pos.x += column_width;
1044 pos.x += hud_fontsize.x;
1046 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1049 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1051 int max_players = 999;
1052 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1054 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1057 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1058 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1059 height /= team_count;
1062 height -= panel_bg_padding * 2; // - padding
1063 max_players = floor(height / (hud_fontsize.y * 1.25));
1064 if(max_players <= 1)
1066 if(max_players == tm.team_size)
1071 entity me = playerslots[current_player];
1073 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1074 panel_size.y += panel_bg_padding * 2;
1077 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1078 if(panel.current_panel_bg != "0")
1079 end_pos.y += panel_bg_border * 2;
1081 if(panel_bg_padding)
1083 panel_pos += '1 1 0' * panel_bg_padding;
1084 panel_size -= '2 2 0' * panel_bg_padding;
1088 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1092 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1094 pos.y += 1.25 * hud_fontsize.y;
1097 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1099 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1102 // print header row and highlight columns
1103 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1105 // fill the table and draw the rows
1106 bool is_self = false;
1107 bool self_shown = false;
1109 for(pl = players.sort_next; pl; pl = pl.sort_next)
1111 if(pl.team != tm.team)
1113 if(i == max_players - 2 && pl != me)
1115 if(!self_shown && me.team == tm.team)
1117 Scoreboard_DrawItem(pos, rgb, me, true, i);
1119 pos.y += 1.25 * hud_fontsize.y;
1123 if(i >= max_players - 1)
1125 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1128 is_self = (pl.sv_entnum == current_player);
1129 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1132 pos.y += 1.25 * hud_fontsize.y;
1136 panel_size.x += panel_bg_padding * 2; // restore initial width
1140 bool Scoreboard_WouldDraw()
1142 if (MUTATOR_CALLHOOK(DrawScoreboard))
1144 else if (QuickMenu_IsOpened())
1146 else if (HUD_Radar_Clickable())
1148 else if (scoreboard_showscores)
1150 else if (intermission == 1)
1152 else if (intermission == 2)
1154 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS)
1155 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1159 else if (scoreboard_showscores_force)
1164 float average_accuracy;
1165 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1169 if (scoreboard_fade_alpha == 1)
1170 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1172 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1174 vector initial_pos = pos;
1176 WepSet weapons_stat = WepSet_GetFromStat();
1177 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1178 int disownedcnt = 0;
1180 FOREACH(Weapons, it != WEP_Null, {
1181 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1183 WepSet set = it.m_wepset;
1184 if(it.spawnflags & WEP_TYPE_OTHER)
1189 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1191 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1198 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1199 if (weapon_cnt <= 0) return pos;
1202 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1204 int columnns = ceil(weapon_cnt / rows);
1206 float weapon_height = 29;
1207 float height = hud_fontsize.y + weapon_height;
1209 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);
1210 pos.y += 1.25 * hud_fontsize.y;
1211 if(panel.current_panel_bg != "0")
1212 pos.y += panel_bg_border;
1215 panel_size.y = height * rows;
1216 panel_size.y += panel_bg_padding * 2;
1218 float panel_bg_alpha_save = panel_bg_alpha;
1219 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1221 panel_bg_alpha = panel_bg_alpha_save;
1223 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1224 if(panel.current_panel_bg != "0")
1225 end_pos.y += panel_bg_border * 2;
1227 if(panel_bg_padding)
1229 panel_pos += '1 1 0' * panel_bg_padding;
1230 panel_size -= '2 2 0' * panel_bg_padding;
1234 vector tmp = panel_size;
1236 float weapon_width = tmp.x / columnns / rows;
1239 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1243 // column highlighting
1244 for (int i = 0; i < columnns; ++i)
1246 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);
1249 for (int i = 0; i < rows; ++i)
1250 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1253 average_accuracy = 0;
1254 int weapons_with_stats = 0;
1256 pos.x += weapon_width / 2;
1258 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1261 Accuracy_LoadColors();
1263 float oldposx = pos.x;
1267 FOREACH(Weapons, it != WEP_Null, {
1268 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1270 WepSet set = it.m_wepset;
1271 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1273 if (it.spawnflags & WEP_TYPE_OTHER)
1277 if (weapon_stats >= 0)
1278 weapon_alpha = sbt_fg_alpha;
1280 weapon_alpha = 0.2 * sbt_fg_alpha;
1283 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1285 if (weapon_stats >= 0) {
1286 weapons_with_stats += 1;
1287 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1290 s = sprintf("%d%%", weapon_stats * 100);
1293 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1295 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1296 rgb = Accuracy_GetColor(weapon_stats);
1298 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1300 tmpos.x += weapon_width * rows;
1301 pos.x += weapon_width * rows;
1302 if (rows == 2 && column == columnns - 1) {
1310 if (weapons_with_stats)
1311 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1313 panel_size.x += panel_bg_padding * 2; // restore initial width
1315 if (scoreboard_acc_fade_alpha == 1)
1317 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1320 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1322 pos.x += hud_fontsize.x * 0.25;
1323 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1324 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1325 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1327 pos.y += hud_fontsize.y;
1332 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1333 float stat_secrets_found, stat_secrets_total;
1334 float stat_monsters_killed, stat_monsters_total;
1338 // get monster stats
1339 stat_monsters_killed = STAT(MONSTERS_KILLED);
1340 stat_monsters_total = STAT(MONSTERS_TOTAL);
1342 // get secrets stats
1343 stat_secrets_found = STAT(SECRETS_FOUND);
1344 stat_secrets_total = STAT(SECRETS_TOTAL);
1346 // get number of rows
1347 if(stat_secrets_total)
1349 if(stat_monsters_total)
1352 // if no rows, return
1356 // draw table header
1357 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1358 pos.y += 1.25 * hud_fontsize.y;
1359 if(panel.current_panel_bg != "0")
1360 pos.y += panel_bg_border;
1363 panel_size.y = hud_fontsize.y * rows;
1364 panel_size.y += panel_bg_padding * 2;
1367 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1368 if(panel.current_panel_bg != "0")
1369 end_pos.y += panel_bg_border * 2;
1371 if(panel_bg_padding)
1373 panel_pos += '1 1 0' * panel_bg_padding;
1374 panel_size -= '2 2 0' * panel_bg_padding;
1378 vector tmp = panel_size;
1381 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1384 if(stat_monsters_total)
1386 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1387 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1391 if(stat_secrets_total)
1393 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1394 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1397 panel_size.x += panel_bg_padding * 2; // restore initial width
1402 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1405 RANKINGS_RECEIVED_CNT = 0;
1406 for (i=RANKINGS_CNT-1; i>=0; --i)
1408 ++RANKINGS_RECEIVED_CNT;
1410 if (RANKINGS_RECEIVED_CNT == 0)
1413 vector hl_rgb = rgb + '0.5 0.5 0.5';
1415 pos.y += hud_fontsize.y;
1416 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1417 pos.y += 1.25 * hud_fontsize.y;
1418 if(panel.current_panel_bg != "0")
1419 pos.y += panel_bg_border;
1424 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1426 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1431 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1433 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1437 float ranksize = 3 * hud_fontsize.x;
1438 float timesize = 5 * hud_fontsize.x;
1439 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1440 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1441 columns = min(columns, RANKINGS_RECEIVED_CNT);
1443 // expand name column to fill the entire row
1444 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1445 namesize += available_space;
1446 columnsize.x += available_space;
1448 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1449 panel_size.y += panel_bg_padding * 2;
1453 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1454 if(panel.current_panel_bg != "0")
1455 end_pos.y += panel_bg_border * 2;
1457 if(panel_bg_padding)
1459 panel_pos += '1 1 0' * panel_bg_padding;
1460 panel_size -= '2 2 0' * panel_bg_padding;
1466 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1468 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1470 int column = 0, j = 0;
1471 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1472 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1479 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1480 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1481 else if(!((j + column) & 1) && sbt_highlight)
1482 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1484 str = count_ordinal(i+1);
1485 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1486 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1487 str = ColorTranslateRGB(grecordholder[i]);
1489 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1490 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1492 pos.y += 1.25 * hud_fontsize.y;
1494 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1498 pos.x += panel_size.x / columns;
1499 pos.y = panel_pos.y;
1502 strfree(zoned_name_self);
1504 panel_size.x += panel_bg_padding * 2; // restore initial width
1508 float scoreboard_time;
1509 bool have_weapon_stats;
1510 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1512 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1514 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1517 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1518 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1524 if (!have_weapon_stats)
1526 FOREACH(Weapons, it != WEP_Null, {
1527 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1528 if (weapon_stats >= 0)
1530 have_weapon_stats = true;
1534 if (!have_weapon_stats)
1541 void Scoreboard_Draw()
1543 if(!autocvar__hud_configure)
1545 if(!hud_draw_maximized) return;
1547 // frametime checks allow to toggle the scoreboard even when the game is paused
1548 if(scoreboard_active) {
1549 if (scoreboard_fade_alpha < 1)
1550 scoreboard_time = time;
1551 if(hud_configure_menu_open == 1)
1552 scoreboard_fade_alpha = 1;
1553 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1554 if (scoreboard_fadeinspeed && frametime)
1555 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1557 scoreboard_fade_alpha = 1;
1558 if(hud_fontsize_str != autocvar_hud_fontsize)
1560 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1561 Scoreboard_initFieldSizes();
1562 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1566 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1567 if (scoreboard_fadeoutspeed && frametime)
1568 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1570 scoreboard_fade_alpha = 0;
1573 if (!scoreboard_fade_alpha)
1575 scoreboard_acc_fade_alpha = 0;
1580 scoreboard_fade_alpha = 0;
1582 if (autocvar_hud_panel_scoreboard_dynamichud)
1585 HUD_Scale_Disable();
1587 if(scoreboard_fade_alpha <= 0)
1589 panel_fade_alpha *= scoreboard_fade_alpha;
1590 HUD_Panel_LoadCvars();
1592 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1593 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1594 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1595 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1596 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1597 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1599 // don't overlap with con_notify
1600 if(!autocvar__hud_configure)
1601 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1603 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1604 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1605 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1606 panel_size.x = fixed_scoreboard_width;
1608 Scoreboard_UpdatePlayerTeams();
1610 vector pos = panel_pos;
1615 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1617 // Begin of Game Info Section
1618 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1619 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1621 // Game Info: Game Type
1622 str = MapInfo_Type_ToText(gametype);
1623 draw_beginBoldFont();
1624 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);
1627 // Game Info: Game Detail
1628 float tl = STAT(TIMELIMIT);
1629 float fl = STAT(FRAGLIMIT);
1630 float ll = STAT(LEADLIMIT);
1631 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1634 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1635 if(!ISGAMETYPE(LMS))
1640 str = strcat(str, "^7 / "); // delimiter
1643 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1644 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1645 (teamscores_label(ts_primary) == "fastest") ? "" :
1646 TranslateScoresLabel(teamscores_label(ts_primary))));
1650 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1651 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1652 (scores_label(ps_primary) == "fastest") ? "" :
1653 TranslateScoresLabel(scores_label(ps_primary))));
1658 if(tl > 0 || fl > 0)
1661 if (ll_and_fl && fl > 0)
1662 str = strcat(str, "^7 & ");
1664 str = strcat(str, "^7 / ");
1669 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1670 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1671 (teamscores_label(ts_primary) == "fastest") ? "" :
1672 TranslateScoresLabel(teamscores_label(ts_primary))));
1676 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1677 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1678 (scores_label(ps_primary) == "fastest") ? "" :
1679 TranslateScoresLabel(scores_label(ps_primary))));
1684 pos.y += sb_gameinfo_type_fontsize.y;
1685 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
1687 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1688 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1689 // End of Game Info Section
1691 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1692 if(panel.current_panel_bg != "0")
1693 pos.y += panel_bg_border;
1695 // Draw the scoreboard
1696 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1699 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1703 vector panel_bg_color_save = panel_bg_color;
1704 vector team_score_baseoffset;
1705 vector team_size_baseoffset;
1706 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1708 // put team score to the left of scoreboard (and team size to the right)
1709 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1710 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1711 if(panel.current_panel_bg != "0")
1713 team_score_baseoffset.x -= panel_bg_border;
1714 team_size_baseoffset.x += panel_bg_border;
1719 // put team score to the right of scoreboard (and team size to the left)
1720 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1721 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1722 if(panel.current_panel_bg != "0")
1724 team_score_baseoffset.x += panel_bg_border;
1725 team_size_baseoffset.x -= panel_bg_border;
1729 int team_size_total = 0;
1730 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1732 // calculate team size total (sum of all team sizes)
1733 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1734 if(tm.team != NUM_SPECTATOR)
1735 team_size_total += tm.team_size;
1738 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1740 if(tm.team == NUM_SPECTATOR)
1745 draw_beginBoldFont();
1746 vector rgb = Team_ColorRGB(tm.team);
1747 str = ftos(tm.(teamscores(ts_primary)));
1748 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1750 // team score on the left (default)
1751 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1755 // team score on the right
1756 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1758 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1760 // team size (if set to show on the side)
1761 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1763 // calculate the starting position for the whole team size info string
1764 str = sprintf("%d/%d", tm.team_size, team_size_total);
1765 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1767 // team size on the left
1768 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1772 // team size on the right
1773 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1775 str = sprintf("%d", tm.team_size);
1776 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1777 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1778 str = sprintf("/%d", team_size_total);
1779 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1783 // secondary score, e.g. keyhunt
1784 if(ts_primary != ts_secondary)
1786 str = ftos(tm.(teamscores(ts_secondary)));
1787 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1790 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1795 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1798 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1801 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1802 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1803 else if(panel_bg_color_team > 0)
1804 panel_bg_color = rgb * panel_bg_color_team;
1806 panel_bg_color = rgb;
1807 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1809 panel_bg_color = panel_bg_color_save;
1813 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1814 if(tm.team != NUM_SPECTATOR)
1817 // display it anyway
1818 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1821 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1822 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1824 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1825 if(race_speedaward) {
1826 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);
1827 pos.y += 1.25 * hud_fontsize.y;
1829 if(race_speedaward_alltimebest) {
1830 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);
1831 pos.y += 1.25 * hud_fontsize.y;
1833 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1836 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1839 for(pl = players.sort_next; pl; pl = pl.sort_next)
1841 if(pl.team == NUM_SPECTATOR)
1843 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1844 if(tm.team == NUM_SPECTATOR)
1846 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1847 draw_beginBoldFont();
1848 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1850 pos.y += 1.25 * hud_fontsize.y;
1852 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1853 pos.y += 1.25 * hud_fontsize.y;
1860 // print information about respawn status
1861 float respawn_time = STAT(RESPAWN_TIME);
1865 if(respawn_time < 0)
1867 // a negative number means we are awaiting respawn, time value is still the same
1868 respawn_time *= -1; // remove mark now that we checked it
1870 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1871 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1873 str = sprintf(_("^1Respawning in ^3%s^1..."),
1874 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1875 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1877 count_seconds(ceil(respawn_time - time))
1881 else if(time < respawn_time)
1883 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1884 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1885 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1887 count_seconds(ceil(respawn_time - time))
1891 else if(time >= respawn_time)
1892 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1894 pos.y += 1.2 * hud_fontsize.y;
1895 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1898 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;