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_dynamichud = false;
88 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
89 bool autocvar_hud_panel_scoreboard_others_showscore = true;
90 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
91 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
92 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
94 // mode 0: returns translated label
95 // mode 1: prints name and description of all the labels
96 string Label_getInfo(string label, int mode)
99 label = "bckills"; // first case in the switch
103 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
104 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
105 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
106 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
107 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
108 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
109 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
110 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
111 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
112 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
113 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
114 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
115 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
116 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
117 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
118 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
119 case "hunts": if (!mode) return CTX(_("SCO^hunts")); else LOG_HELP(strcat("^3", "hunts", " ^7", _("Number of successful hunter rounds (Survival)")));
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 "survivals": if (!mode) return CTX(_("SCO^survivals")); else LOG_HELP(strcat("^3", "survivals", " ^7", _("Number of rounds survived (Survival)")));
143 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
144 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
145 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
146 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
147 default: return label;
152 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
153 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
155 void Scoreboard_InitScores()
159 ps_primary = ps_secondary = NULL;
160 ts_primary = ts_secondary = -1;
161 FOREACH(Scores, true, {
162 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
163 if(f == SFL_SORT_PRIO_PRIMARY)
165 if(f == SFL_SORT_PRIO_SECONDARY)
168 if(ps_secondary == NULL)
169 ps_secondary = ps_primary;
171 for(i = 0; i < MAX_TEAMSCORE; ++i)
173 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
174 if(f == SFL_SORT_PRIO_PRIMARY)
176 if(f == SFL_SORT_PRIO_SECONDARY)
179 if(ts_secondary == -1)
180 ts_secondary = ts_primary;
182 Cmd_Scoreboard_SetFields(0);
186 void Scoreboard_UpdatePlayerTeams()
190 for(pl = players.sort_next; pl; pl = pl.sort_next)
193 int Team = entcs_GetScoreTeam(pl.sv_entnum);
194 if(SetTeam(pl, Team))
197 Scoreboard_UpdatePlayerPos(pl);
201 pl = players.sort_next;
206 print(strcat("PNUM: ", ftos(num), "\n"));
211 int Scoreboard_CompareScore(int vl, int vr, int f)
213 TC(int, vl); TC(int, vr); TC(int, f);
214 if(f & SFL_ZERO_IS_WORST)
216 if(vl == 0 && vr != 0)
218 if(vl != 0 && vr == 0)
222 return IS_INCREASING(f);
224 return IS_DECREASING(f);
228 float Scoreboard_ComparePlayerScores(entity left, entity right)
231 vl = entcs_GetTeam(left.sv_entnum);
232 vr = entcs_GetTeam(right.sv_entnum);
244 if(vl == NUM_SPECTATOR)
246 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
248 if(!left.gotscores && right.gotscores)
253 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
257 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
261 FOREACH(Scores, true, {
262 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
263 if (r >= 0) return r;
266 if (left.sv_entnum < right.sv_entnum)
272 void Scoreboard_UpdatePlayerPos(entity player)
275 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
277 SORT_SWAP(player, ent);
279 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
281 SORT_SWAP(ent, player);
285 float Scoreboard_CompareTeamScores(entity left, entity right)
289 if(left.team == NUM_SPECTATOR)
291 if(right.team == NUM_SPECTATOR)
294 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
298 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
302 for(i = 0; i < MAX_TEAMSCORE; ++i)
304 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
309 if (left.team < right.team)
315 void Scoreboard_UpdateTeamPos(entity Team)
318 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
320 SORT_SWAP(Team, ent);
322 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
324 SORT_SWAP(ent, Team);
328 void Cmd_Scoreboard_Help()
330 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
331 LOG_HELP(_("Usage:"));
332 LOG_HELP("^2scoreboard_columns_set ^3default");
333 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
334 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
335 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
336 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
337 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
338 LOG_HELP(_("The following field names are recognized (case insensitive):"));
344 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
345 "of game types, then a slash, to make the field show up only in these\n"
346 "or in all but these game types. You can also specify 'all' as a\n"
347 "field to show all fields available for the current game mode."));
350 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
351 "include/exclude ALL teams/noteams game modes."));
354 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
355 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
356 "right of the vertical bar aligned to the right."));
357 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
358 "other gamemodes except DM."));
361 // NOTE: adding a gametype with ? to not warn for an optional field
362 // make sure it's excluded in a previous exclusive rule, if any
363 // otherwise the previous exclusive rule warns anyway
364 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
365 #define SCOREBOARD_DEFAULT_COLUMNS \
366 "ping pl fps name |" \
367 " -teams,rc,cts,inv,lms,sv/kills +ft,tdm/kills ?+rc,inv/kills" \
368 " -teams,lms,sv/deaths +ft,tdm/deaths" \
370 " -teams,lms,rc,cts,inv,ka,sv/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
371 " -cts,dm,tdm,ka,ft,sv/frags" /* tdm already has this in "score" */ \
372 " +tdm,ft,dom,ons,as/teamkills"\
373 " -rc,cts,nb,sv/dmg -rc,cts,nb,sv/dmgtaken" \
374 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
375 " +lms/lives +lms/rank" \
376 " +kh/kckills +kh/losses +kh/caps" \
377 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
378 " +as/objectives +nb/faults +nb/goals" \
379 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
380 " +dom/ticks +dom/takes" \
381 " +sv/survivals +sv/hunts" \
382 " -lms,rc,cts,inv,nb/score"
384 void Cmd_Scoreboard_SetFields(int argc)
389 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
393 return; // do nothing, we don't know gametype and scores yet
395 // sbt_fields uses strunzone on the titles!
396 if(!sbt_field_title[0])
397 for(i = 0; i < MAX_SBT_FIELDS; ++i)
398 sbt_field_title[i] = strzone("(null)");
400 // TODO: re enable with gametype dependant cvars?
401 if(argc < 3) // no arguments provided
402 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
405 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
409 if(argv(2) == "default" || argv(2) == "expand_default")
411 if(argv(2) == "expand_default")
412 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
413 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
415 else if(argv(2) == "all")
417 string s = "ping pl name |"; // scores without a label
418 FOREACH(Scores, true, {
420 if(it != ps_secondary)
421 if(scores_label(it) != "")
422 s = strcat(s, " ", scores_label(it));
424 if(ps_secondary != ps_primary)
425 s = strcat(s, " ", scores_label(ps_secondary));
426 s = strcat(s, " ", scores_label(ps_primary));
427 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
434 hud_fontsize = HUD_GetFontsize("hud_fontsize");
436 for(i = 1; i < argc - 1; ++i)
439 bool nocomplain = false;
440 if(substring(str, 0, 1) == "?")
443 str = substring(str, 1, strlen(str) - 1);
446 slash = strstrofs(str, "/", 0);
449 pattern = substring(str, 0, slash);
450 str = substring(str, slash + 1, strlen(str) - (slash + 1));
452 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
456 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
457 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
458 str = strtolower(str);
463 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
464 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
465 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
466 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
467 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
468 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
469 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
470 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
471 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
472 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
475 FOREACH(Scores, true, {
476 if (str == strtolower(scores_label(it))) {
478 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
488 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
492 sbt_field[sbt_num_fields] = j;
495 if(j == ps_secondary)
496 have_secondary = true;
501 if(sbt_num_fields >= MAX_SBT_FIELDS)
505 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
507 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
508 have_secondary = true;
509 if(ps_primary == ps_secondary)
510 have_secondary = true;
511 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
513 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
517 strunzone(sbt_field_title[sbt_num_fields]);
518 for(i = sbt_num_fields; i > 0; --i)
520 sbt_field_title[i] = sbt_field_title[i-1];
521 sbt_field_size[i] = sbt_field_size[i-1];
522 sbt_field[i] = sbt_field[i-1];
524 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
525 sbt_field[0] = SP_NAME;
527 LOG_INFO("fixed missing field 'name'");
531 strunzone(sbt_field_title[sbt_num_fields]);
532 for(i = sbt_num_fields; i > 1; --i)
534 sbt_field_title[i] = sbt_field_title[i-1];
535 sbt_field_size[i] = sbt_field_size[i-1];
536 sbt_field[i] = sbt_field[i-1];
538 sbt_field_title[1] = strzone("|");
539 sbt_field[1] = SP_SEPARATOR;
540 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
542 LOG_INFO("fixed missing field '|'");
545 else if(!have_separator)
547 strcpy(sbt_field_title[sbt_num_fields], "|");
548 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
549 sbt_field[sbt_num_fields] = SP_SEPARATOR;
551 LOG_INFO("fixed missing field '|'");
555 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
556 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
557 sbt_field[sbt_num_fields] = ps_secondary;
559 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
563 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
564 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
565 sbt_field[sbt_num_fields] = ps_primary;
567 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
571 sbt_field[sbt_num_fields] = SP_END;
575 vector sbt_field_rgb;
576 string sbt_field_icon0;
577 string sbt_field_icon1;
578 string sbt_field_icon2;
579 vector sbt_field_icon0_rgb;
580 vector sbt_field_icon1_rgb;
581 vector sbt_field_icon2_rgb;
582 string Scoreboard_GetName(entity pl)
584 if(ready_waiting && pl.ready)
586 sbt_field_icon0 = "gfx/scoreboard/player_ready";
590 int f = entcs_GetClientColors(pl.sv_entnum);
592 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
593 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
594 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
595 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
596 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
599 return entcs_GetName(pl.sv_entnum);
602 string Scoreboard_GetField(entity pl, PlayerScoreField field)
604 float tmp, num, denom;
607 sbt_field_rgb = '1 1 1';
608 sbt_field_icon0 = "";
609 sbt_field_icon1 = "";
610 sbt_field_icon2 = "";
611 sbt_field_icon0_rgb = '1 1 1';
612 sbt_field_icon1_rgb = '1 1 1';
613 sbt_field_icon2_rgb = '1 1 1';
618 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
619 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
623 tmp = max(0, min(220, f-80)) / 220;
624 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
630 f = pl.ping_packetloss;
631 tmp = pl.ping_movementloss;
632 if(f == 0 && tmp == 0)
634 str = ftos(ceil(f * 100));
636 str = strcat(str, "~", ftos(ceil(tmp * 100)));
637 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
638 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
642 return Scoreboard_GetName(pl);
645 f = pl.(scores(SP_KILLS));
646 f -= pl.(scores(SP_SUICIDES));
650 num = pl.(scores(SP_KILLS));
651 denom = pl.(scores(SP_DEATHS));
654 sbt_field_rgb = '0 1 0';
655 str = sprintf("%d", num);
656 } else if(num <= 0) {
657 sbt_field_rgb = '1 0 0';
658 str = sprintf("%.1f", num/denom);
660 str = sprintf("%.1f", num/denom);
664 f = pl.(scores(SP_KILLS));
665 f -= pl.(scores(SP_DEATHS));
668 sbt_field_rgb = '0 1 0';
670 sbt_field_rgb = '1 1 1';
672 sbt_field_rgb = '1 0 0';
678 float elo = pl.(scores(SP_ELO));
680 case -1: return "...";
681 case -2: return _("N/A");
682 default: return ftos(elo);
688 float fps = pl.(scores(SP_FPS));
691 sbt_field_rgb = '1 1 1';
692 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
694 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
695 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
699 case SP_DMG: case SP_DMGTAKEN:
700 return sprintf("%.1f k", pl.(scores(field)) / 1000);
702 default: case SP_SCORE:
703 tmp = pl.(scores(field));
704 f = scores_flags(field);
705 if(field == ps_primary)
706 sbt_field_rgb = '1 1 0';
707 else if(field == ps_secondary)
708 sbt_field_rgb = '0 1 1';
710 sbt_field_rgb = '1 1 1';
711 return ScoreString(f, tmp);
716 float sbt_fixcolumnwidth_len;
717 float sbt_fixcolumnwidth_iconlen;
718 float sbt_fixcolumnwidth_marginlen;
720 string Scoreboard_FixColumnWidth(int i, string str)
726 sbt_fixcolumnwidth_iconlen = 0;
728 if(sbt_field_icon0 != "")
730 sz = draw_getimagesize(sbt_field_icon0);
732 if(sbt_fixcolumnwidth_iconlen < f)
733 sbt_fixcolumnwidth_iconlen = f;
736 if(sbt_field_icon1 != "")
738 sz = draw_getimagesize(sbt_field_icon1);
740 if(sbt_fixcolumnwidth_iconlen < f)
741 sbt_fixcolumnwidth_iconlen = f;
744 if(sbt_field_icon2 != "")
746 sz = draw_getimagesize(sbt_field_icon2);
748 if(sbt_fixcolumnwidth_iconlen < f)
749 sbt_fixcolumnwidth_iconlen = f;
752 if(sbt_fixcolumnwidth_iconlen != 0)
754 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
755 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
758 sbt_fixcolumnwidth_marginlen = 0;
760 if(sbt_field[i] == SP_NAME) // name gets all remaining space
763 float remaining_space = 0;
764 for(j = 0; j < sbt_num_fields; ++j)
766 if (sbt_field[i] != SP_SEPARATOR)
767 remaining_space += sbt_field_size[j] + hud_fontsize.x;
768 sbt_field_size[i] = panel_size.x - remaining_space;
770 if (sbt_fixcolumnwidth_iconlen != 0)
771 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
772 float namesize = panel_size.x - remaining_space;
773 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
774 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
776 max_namesize = vid_conwidth - remaining_space;
779 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
781 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
782 if(sbt_field_size[i] < f)
783 sbt_field_size[i] = f;
788 void Scoreboard_initFieldSizes()
790 for(int i = 0; i < sbt_num_fields; ++i)
792 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
793 Scoreboard_FixColumnWidth(i, "");
797 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
800 vector column_dim = eY * panel_size.y;
802 column_dim.y -= 1.25 * hud_fontsize.y;
803 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
804 pos.x += hud_fontsize.x * 0.5;
805 for(i = 0; i < sbt_num_fields; ++i)
807 if(sbt_field[i] == SP_SEPARATOR)
809 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
812 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
813 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
814 pos.x += column_dim.x;
816 if(sbt_field[i] == SP_SEPARATOR)
818 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
819 for(i = sbt_num_fields - 1; i > 0; --i)
821 if(sbt_field[i] == SP_SEPARATOR)
824 pos.x -= sbt_field_size[i];
829 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
830 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
833 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
834 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
835 pos.x -= hud_fontsize.x;
840 pos.y += 1.25 * hud_fontsize.y;
844 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
846 TC(bool, is_self); TC(int, pl_number);
848 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
850 vector h_pos = item_pos;
851 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
852 // alternated rows highlighting
854 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
855 else if((sbt_highlight) && (!(pl_number % 2)))
856 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
858 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
860 vector pos = item_pos;
861 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
863 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);
865 pos.x += hud_fontsize.x * 0.5;
866 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
867 vector tmp = '0 0 0';
869 PlayerScoreField field;
870 for(i = 0; i < sbt_num_fields; ++i)
872 field = sbt_field[i];
873 if(field == SP_SEPARATOR)
876 if(is_spec && field != SP_NAME && field != SP_PING) {
877 pos.x += sbt_field_size[i] + hud_fontsize.x;
880 str = Scoreboard_GetField(pl, field);
881 str = Scoreboard_FixColumnWidth(i, str);
883 pos.x += sbt_field_size[i] + hud_fontsize.x;
885 if(field == SP_NAME) {
886 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
887 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
889 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
890 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
893 tmp.x = sbt_field_size[i] + hud_fontsize.x;
894 if(sbt_field_icon0 != "")
895 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
896 if(sbt_field_icon1 != "")
897 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
898 if(sbt_field_icon2 != "")
899 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
902 if(sbt_field[i] == SP_SEPARATOR)
904 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
905 for(i = sbt_num_fields-1; i > 0; --i)
907 field = sbt_field[i];
908 if(field == SP_SEPARATOR)
911 if(is_spec && field != SP_NAME && field != SP_PING) {
912 pos.x -= sbt_field_size[i] + hud_fontsize.x;
916 str = Scoreboard_GetField(pl, field);
917 str = Scoreboard_FixColumnWidth(i, str);
919 if(field == SP_NAME) {
920 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
921 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
923 tmp.x = sbt_fixcolumnwidth_len;
924 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
927 tmp.x = sbt_field_size[i];
928 if(sbt_field_icon0 != "")
929 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
930 if(sbt_field_icon1 != "")
931 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
932 if(sbt_field_icon2 != "")
933 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
934 pos.x -= sbt_field_size[i] + hud_fontsize.x;
939 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
942 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
945 vector h_pos = item_pos;
946 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
948 bool complete = (this_team == NUM_SPECTATOR);
951 if((sbt_highlight) && (!(pl_number % 2)))
952 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
954 vector pos = item_pos;
955 pos.x += hud_fontsize.x * 0.5;
956 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
958 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
960 width_limit -= stringwidth("...", false, hud_fontsize);
961 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
962 static float max_name_width = 0;
965 float min_fieldsize = 0;
966 float fieldpadding = hud_fontsize.x * 0.25;
967 if(this_team == NUM_SPECTATOR)
969 if(autocvar_hud_panel_scoreboard_spectators_showping)
970 min_fieldsize = stringwidth("999", false, hud_fontsize);
972 else if(autocvar_hud_panel_scoreboard_others_showscore)
973 min_fieldsize = stringwidth("99", false, hud_fontsize);
974 for(i = 0; pl; pl = pl.sort_next)
976 if(pl.team != this_team)
982 if(this_team == NUM_SPECTATOR)
984 if(autocvar_hud_panel_scoreboard_spectators_showping)
985 field = Scoreboard_GetField(pl, SP_PING);
987 else if(autocvar_hud_panel_scoreboard_others_showscore)
988 field = Scoreboard_GetField(pl, SP_SCORE);
990 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
991 float column_width = stringwidth(str, true, hud_fontsize);
992 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
994 if(column_width > max_name_width)
995 max_name_width = column_width;
996 column_width = max_name_width;
1000 fieldsize = stringwidth(field, false, hud_fontsize);
1001 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1004 if(pos.x + column_width > width_limit)
1009 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1014 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1015 pos.y += hud_fontsize.y * 1.25;
1019 vector name_pos = pos;
1020 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1021 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1022 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1025 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1026 h_size.y = hud_fontsize.y;
1027 vector field_pos = pos;
1028 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1029 field_pos.x += column_width - h_size.x;
1031 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1032 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1033 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1037 h_size.x = column_width + hud_fontsize.x * 0.25;
1038 h_size.y = hud_fontsize.y;
1039 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1041 pos.x += column_width;
1042 pos.x += hud_fontsize.x;
1044 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1047 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1049 int max_players = 999;
1050 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1052 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1055 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1056 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1057 height /= team_count;
1060 height -= panel_bg_padding * 2; // - padding
1061 max_players = floor(height / (hud_fontsize.y * 1.25));
1062 if(max_players <= 1)
1064 if(max_players == tm.team_size)
1069 entity me = playerslots[current_player];
1071 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1072 panel_size.y += panel_bg_padding * 2;
1075 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1076 if(panel.current_panel_bg != "0")
1077 end_pos.y += panel_bg_border * 2;
1079 if(panel_bg_padding)
1081 panel_pos += '1 1 0' * panel_bg_padding;
1082 panel_size -= '2 2 0' * panel_bg_padding;
1086 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1090 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1092 pos.y += 1.25 * hud_fontsize.y;
1095 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1097 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1100 // print header row and highlight columns
1101 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1103 // fill the table and draw the rows
1104 bool is_self = false;
1105 bool self_shown = false;
1107 for(pl = players.sort_next; pl; pl = pl.sort_next)
1109 if(pl.team != tm.team)
1111 if(i == max_players - 2 && pl != me)
1113 if(!self_shown && me.team == tm.team)
1115 Scoreboard_DrawItem(pos, rgb, me, true, i);
1117 pos.y += 1.25 * hud_fontsize.y;
1121 if(i >= max_players - 1)
1123 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1126 is_self = (pl.sv_entnum == current_player);
1127 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1130 pos.y += 1.25 * hud_fontsize.y;
1134 panel_size.x += panel_bg_padding * 2; // restore initial width
1138 bool Scoreboard_WouldDraw()
1140 if (MUTATOR_CALLHOOK(DrawScoreboard))
1142 else if (QuickMenu_IsOpened())
1144 else if (HUD_Radar_Clickable())
1146 else if (scoreboard_showscores)
1148 else if (intermission == 1)
1150 else if (intermission == 2)
1152 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1153 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1157 else if (scoreboard_showscores_force)
1162 float average_accuracy;
1163 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1167 if (scoreboard_fade_alpha == 1)
1168 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1170 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1172 vector initial_pos = pos;
1174 WepSet weapons_stat = WepSet_GetFromStat();
1175 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1176 int disownedcnt = 0;
1178 FOREACH(Weapons, it != WEP_Null, {
1179 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1181 WepSet set = it.m_wepset;
1182 if(it.spawnflags & WEP_TYPE_OTHER)
1187 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1189 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1196 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1197 if (weapon_cnt <= 0) return pos;
1200 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1202 int columnns = ceil(weapon_cnt / rows);
1204 float weapon_height = 29;
1205 float height = hud_fontsize.y + weapon_height;
1207 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);
1208 pos.y += 1.25 * hud_fontsize.y;
1209 if(panel.current_panel_bg != "0")
1210 pos.y += panel_bg_border;
1213 panel_size.y = height * rows;
1214 panel_size.y += panel_bg_padding * 2;
1216 float panel_bg_alpha_save = panel_bg_alpha;
1217 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1219 panel_bg_alpha = panel_bg_alpha_save;
1221 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1222 if(panel.current_panel_bg != "0")
1223 end_pos.y += panel_bg_border * 2;
1225 if(panel_bg_padding)
1227 panel_pos += '1 1 0' * panel_bg_padding;
1228 panel_size -= '2 2 0' * panel_bg_padding;
1232 vector tmp = panel_size;
1234 float weapon_width = tmp.x / columnns / rows;
1237 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1241 // column highlighting
1242 for (int i = 0; i < columnns; ++i)
1244 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);
1247 for (int i = 0; i < rows; ++i)
1248 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1251 average_accuracy = 0;
1252 int weapons_with_stats = 0;
1254 pos.x += weapon_width / 2;
1256 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1259 Accuracy_LoadColors();
1261 float oldposx = pos.x;
1265 FOREACH(Weapons, it != WEP_Null, {
1266 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1268 WepSet set = it.m_wepset;
1269 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1271 if (it.spawnflags & WEP_TYPE_OTHER)
1275 if (weapon_stats >= 0)
1276 weapon_alpha = sbt_fg_alpha;
1278 weapon_alpha = 0.2 * sbt_fg_alpha;
1281 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1283 if (weapon_stats >= 0) {
1284 weapons_with_stats += 1;
1285 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1288 s = sprintf("%d%%", weapon_stats * 100);
1291 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1293 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1294 rgb = Accuracy_GetColor(weapon_stats);
1296 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1298 tmpos.x += weapon_width * rows;
1299 pos.x += weapon_width * rows;
1300 if (rows == 2 && column == columnns - 1) {
1308 if (weapons_with_stats)
1309 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1311 panel_size.x += panel_bg_padding * 2; // restore initial width
1313 if (scoreboard_acc_fade_alpha == 1)
1315 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1318 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1320 pos.x += hud_fontsize.x * 0.25;
1321 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1322 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1323 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1325 pos.y += hud_fontsize.y;
1330 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1331 float stat_secrets_found, stat_secrets_total;
1332 float stat_monsters_killed, stat_monsters_total;
1336 // get monster stats
1337 stat_monsters_killed = STAT(MONSTERS_KILLED);
1338 stat_monsters_total = STAT(MONSTERS_TOTAL);
1340 // get secrets stats
1341 stat_secrets_found = STAT(SECRETS_FOUND);
1342 stat_secrets_total = STAT(SECRETS_TOTAL);
1344 // get number of rows
1345 if(stat_secrets_total)
1347 if(stat_monsters_total)
1350 // if no rows, return
1354 // draw table header
1355 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1356 pos.y += 1.25 * hud_fontsize.y;
1357 if(panel.current_panel_bg != "0")
1358 pos.y += panel_bg_border;
1361 panel_size.y = hud_fontsize.y * rows;
1362 panel_size.y += panel_bg_padding * 2;
1365 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1366 if(panel.current_panel_bg != "0")
1367 end_pos.y += panel_bg_border * 2;
1369 if(panel_bg_padding)
1371 panel_pos += '1 1 0' * panel_bg_padding;
1372 panel_size -= '2 2 0' * panel_bg_padding;
1376 vector tmp = panel_size;
1379 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1382 if(stat_monsters_total)
1384 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1385 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1389 if(stat_secrets_total)
1391 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1392 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1395 panel_size.x += panel_bg_padding * 2; // restore initial width
1400 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1403 RANKINGS_RECEIVED_CNT = 0;
1404 for (i=RANKINGS_CNT-1; i>=0; --i)
1406 ++RANKINGS_RECEIVED_CNT;
1408 if (RANKINGS_RECEIVED_CNT == 0)
1411 vector hl_rgb = rgb + '0.5 0.5 0.5';
1413 pos.y += hud_fontsize.y;
1414 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1415 pos.y += 1.25 * hud_fontsize.y;
1416 if(panel.current_panel_bg != "0")
1417 pos.y += panel_bg_border;
1422 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1424 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1429 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1431 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1435 float ranksize = 3 * hud_fontsize.x;
1436 float timesize = 5 * hud_fontsize.x;
1437 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1438 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1439 columns = min(columns, RANKINGS_RECEIVED_CNT);
1441 // expand name column to fill the entire row
1442 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1443 namesize += available_space;
1444 columnsize.x += available_space;
1446 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1447 panel_size.y += panel_bg_padding * 2;
1451 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1452 if(panel.current_panel_bg != "0")
1453 end_pos.y += panel_bg_border * 2;
1455 if(panel_bg_padding)
1457 panel_pos += '1 1 0' * panel_bg_padding;
1458 panel_size -= '2 2 0' * panel_bg_padding;
1464 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1466 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1468 int column = 0, j = 0;
1469 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1470 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1477 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1478 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1479 else if(!((j + column) & 1) && sbt_highlight)
1480 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1482 str = count_ordinal(i+1);
1483 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1484 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1485 str = ColorTranslateRGB(grecordholder[i]);
1487 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1488 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1490 pos.y += 1.25 * hud_fontsize.y;
1492 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1496 pos.x += panel_size.x / columns;
1497 pos.y = panel_pos.y;
1500 strfree(zoned_name_self);
1502 panel_size.x += panel_bg_padding * 2; // restore initial width
1506 float scoreboard_time;
1507 bool have_weapon_stats;
1508 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1510 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1512 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1515 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1516 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1522 if (!have_weapon_stats)
1524 FOREACH(Weapons, it != WEP_Null, {
1525 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1526 if (weapon_stats >= 0)
1528 have_weapon_stats = true;
1532 if (!have_weapon_stats)
1539 void Scoreboard_Draw()
1541 if(!autocvar__hud_configure)
1543 if(!hud_draw_maximized) return;
1545 // frametime checks allow to toggle the scoreboard even when the game is paused
1546 if(scoreboard_active) {
1547 if (scoreboard_fade_alpha < 1)
1548 scoreboard_time = time;
1549 if(hud_configure_menu_open == 1)
1550 scoreboard_fade_alpha = 1;
1551 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1552 if (scoreboard_fadeinspeed && frametime)
1553 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1555 scoreboard_fade_alpha = 1;
1556 if(hud_fontsize_str != autocvar_hud_fontsize)
1558 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1559 Scoreboard_initFieldSizes();
1560 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1564 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1565 if (scoreboard_fadeoutspeed && frametime)
1566 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1568 scoreboard_fade_alpha = 0;
1571 if (!scoreboard_fade_alpha)
1573 scoreboard_acc_fade_alpha = 0;
1578 scoreboard_fade_alpha = 0;
1580 if (autocvar_hud_panel_scoreboard_dynamichud)
1583 HUD_Scale_Disable();
1585 if(scoreboard_fade_alpha <= 0)
1587 panel_fade_alpha *= scoreboard_fade_alpha;
1588 HUD_Panel_LoadCvars();
1590 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1591 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1592 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1593 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1594 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1595 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1597 // don't overlap with con_notify
1598 if(!autocvar__hud_configure)
1599 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1601 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1602 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1603 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1604 panel_size.x = fixed_scoreboard_width;
1606 Scoreboard_UpdatePlayerTeams();
1608 vector pos = panel_pos;
1613 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1615 // Begin of Game Info Section
1616 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1617 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1619 // Game Info: Game Type
1620 str = MapInfo_Type_ToText(gametype);
1621 draw_beginBoldFont();
1622 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);
1625 // Game Info: Game Detail
1626 float tl = STAT(TIMELIMIT);
1627 float fl = STAT(FRAGLIMIT);
1628 float ll = STAT(LEADLIMIT);
1629 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1632 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1633 if(!gametype.m_hidelimits)
1638 str = strcat(str, "^7 / "); // delimiter
1641 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1642 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1643 (teamscores_label(ts_primary) == "fastest") ? "" :
1644 TranslateScoresLabel(teamscores_label(ts_primary))));
1648 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1649 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1650 (scores_label(ps_primary) == "fastest") ? "" :
1651 TranslateScoresLabel(scores_label(ps_primary))));
1656 if(tl > 0 || fl > 0)
1659 if (ll_and_fl && fl > 0)
1660 str = strcat(str, "^7 & ");
1662 str = strcat(str, "^7 / ");
1667 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1668 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1669 (teamscores_label(ts_primary) == "fastest") ? "" :
1670 TranslateScoresLabel(teamscores_label(ts_primary))));
1674 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1675 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1676 (scores_label(ps_primary) == "fastest") ? "" :
1677 TranslateScoresLabel(scores_label(ps_primary))));
1682 pos.y += sb_gameinfo_type_fontsize.y;
1683 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
1685 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1686 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1687 // End of Game Info Section
1689 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1690 if(panel.current_panel_bg != "0")
1691 pos.y += panel_bg_border;
1693 // Draw the scoreboard
1694 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1697 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1701 vector panel_bg_color_save = panel_bg_color;
1702 vector team_score_baseoffset;
1703 vector team_size_baseoffset;
1704 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1706 // put team score to the left of scoreboard (and team size to the right)
1707 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1708 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1709 if(panel.current_panel_bg != "0")
1711 team_score_baseoffset.x -= panel_bg_border;
1712 team_size_baseoffset.x += panel_bg_border;
1717 // put team score to the right of scoreboard (and team size to the left)
1718 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1719 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1720 if(panel.current_panel_bg != "0")
1722 team_score_baseoffset.x += panel_bg_border;
1723 team_size_baseoffset.x -= panel_bg_border;
1727 int team_size_total = 0;
1728 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1730 // calculate team size total (sum of all team sizes)
1731 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1732 if(tm.team != NUM_SPECTATOR)
1733 team_size_total += tm.team_size;
1736 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1738 if(tm.team == NUM_SPECTATOR)
1743 draw_beginBoldFont();
1744 vector rgb = Team_ColorRGB(tm.team);
1745 str = ftos(tm.(teamscores(ts_primary)));
1746 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1748 // team score on the left (default)
1749 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1753 // team score on the right
1754 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1756 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1758 // team size (if set to show on the side)
1759 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1761 // calculate the starting position for the whole team size info string
1762 str = sprintf("%d/%d", tm.team_size, team_size_total);
1763 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1765 // team size on the left
1766 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1770 // team size on the right
1771 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1773 str = sprintf("%d", tm.team_size);
1774 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1775 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1776 str = sprintf("/%d", team_size_total);
1777 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1781 // secondary score, e.g. keyhunt
1782 if(ts_primary != ts_secondary)
1784 str = ftos(tm.(teamscores(ts_secondary)));
1785 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1788 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1793 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1796 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1799 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1800 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1801 else if(panel_bg_color_team > 0)
1802 panel_bg_color = rgb * panel_bg_color_team;
1804 panel_bg_color = rgb;
1805 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1807 panel_bg_color = panel_bg_color_save;
1811 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1812 if(tm.team != NUM_SPECTATOR)
1815 // display it anyway
1816 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1819 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1820 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1822 if(MUTATOR_CALLHOOK(ShowRankings)) {
1823 string ranktitle = M_ARGV(0, string);
1824 if(race_speedaward) {
1825 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);
1826 pos.y += 1.25 * hud_fontsize.y;
1828 if(race_speedaward_alltimebest) {
1829 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);
1830 pos.y += 1.25 * hud_fontsize.y;
1832 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1835 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1838 for(pl = players.sort_next; pl; pl = pl.sort_next)
1840 if(pl.team == NUM_SPECTATOR)
1842 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1843 if(tm.team == NUM_SPECTATOR)
1845 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1846 draw_beginBoldFont();
1847 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1849 pos.y += 1.25 * hud_fontsize.y;
1851 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1852 pos.y += 1.25 * hud_fontsize.y;
1859 // print information about respawn status
1860 float respawn_time = STAT(RESPAWN_TIME);
1864 if(respawn_time < 0)
1866 // a negative number means we are awaiting respawn, time value is still the same
1867 respawn_time *= -1; // remove mark now that we checked it
1869 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1870 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1872 str = sprintf(_("^1Respawning in ^3%s^1..."),
1873 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1874 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1876 count_seconds(ceil(respawn_time - time))
1880 else if(time < respawn_time)
1882 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1883 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1884 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1886 count_seconds(ceil(respawn_time - time))
1890 else if(time >= respawn_time)
1891 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1893 pos.y += 1.2 * hud_fontsize.y;
1894 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1897 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;