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_INFO(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
106 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_INFO(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
107 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_INFO(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_INFO(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
109 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_INFO(strcat("^3", "deaths", " ^7", _("Number of deaths")));
110 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_INFO(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
111 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_INFO(strcat("^3", "dmg", " ^7", _("The total damage done")));
112 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_INFO(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
113 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_INFO(strcat("^3", "drops", " ^7", _("Number of flag drops")));
114 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_INFO(strcat("^3", "elo", " ^7", _("Player ELO")));
115 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_INFO(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
116 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_INFO(strcat("^3", "faults", " ^7", _("Number of faults committed")));
117 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_INFO(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
118 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_INFO(strcat("^3", "fps", " ^7", _("FPS")));
119 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_INFO(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
120 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_INFO(strcat("^3", "goals", " ^7", _("Number of goals scored")));
121 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_INFO(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
122 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_INFO(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
123 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_INFO(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
124 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_INFO(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
125 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_INFO(strcat("^3", "kills", " ^7", _("Number of kills")));
126 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_INFO(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
127 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_INFO(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
128 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_INFO(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
129 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_INFO(strcat("^3", "name", " ^7", _("Player name")));
130 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_INFO(strcat("^3", "nick", " ^7", _("Player name")));
131 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_INFO(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
132 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_INFO(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
133 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_INFO(strcat("^3", "ping", " ^7", _("Ping time")));
134 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_INFO(strcat("^3", "pl", " ^7", _("Packet loss")));
135 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_INFO(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
136 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_INFO(strcat("^3", "rank", " ^7", _("Player rank")));
137 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_INFO(strcat("^3", "returns", " ^7", _("Number of flag returns")));
138 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_INFO(strcat("^3", "revivals", " ^7", _("Number of revivals")));
139 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_INFO(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
140 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_INFO(strcat("^3", "score", " ^7", _("Total score")));
141 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_INFO(strcat("^3", "suicides", " ^7", _("Number of suicides")));
142 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_INFO(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
143 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_INFO(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
144 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_INFO(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
145 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_INFO(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
146 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_INFO(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_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
331 LOG_INFO(_("Usage:"));
332 LOG_INFO("^2scoreboard_columns_set ^3default");
333 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
334 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
335 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
336 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
337 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
338 LOG_INFO(_("The following field names are recognized (case insensitive):"));
344 LOG_INFO(_("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_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
351 "include/exclude ALL teams/noteams game modes."));
354 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
355 LOG_INFO(_("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_INFO(_("'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/kills +ft,tdm/kills ?+rc,inv/kills" \
368 " -teams,lms/deaths +ft,tdm/deaths" \
370 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
371 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
372 " +tdm,ft,dom,ons,as/teamkills"\
373 " -rc,cts,nb/dmg -rc,cts,nb/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 " -lms,rc,cts,inv,nb/score"
383 void Cmd_Scoreboard_SetFields(int argc)
388 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
392 return; // do nothing, we don't know gametype and scores yet
394 // sbt_fields uses strunzone on the titles!
395 if(!sbt_field_title[0])
396 for(i = 0; i < MAX_SBT_FIELDS; ++i)
397 sbt_field_title[i] = strzone("(null)");
399 // TODO: re enable with gametype dependant cvars?
400 if(argc < 3) // no arguments provided
401 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
404 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
408 if(argv(2) == "default" || argv(2) == "expand_default")
410 if(argv(2) == "expand_default")
411 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
412 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
414 else if(argv(2) == "all")
416 string s = "ping pl name |"; // scores without a label
417 FOREACH(Scores, true, {
419 if(it != ps_secondary)
420 if(scores_label(it) != "")
421 s = strcat(s, " ", scores_label(it));
423 if(ps_secondary != ps_primary)
424 s = strcat(s, " ", scores_label(ps_secondary));
425 s = strcat(s, " ", scores_label(ps_primary));
426 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
433 hud_fontsize = HUD_GetFontsize("hud_fontsize");
435 for(i = 1; i < argc - 1; ++i)
438 bool nocomplain = false;
439 if(substring(str, 0, 1) == "?")
442 str = substring(str, 1, strlen(str) - 1);
445 slash = strstrofs(str, "/", 0);
448 pattern = substring(str, 0, slash);
449 str = substring(str, slash + 1, strlen(str) - (slash + 1));
451 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
455 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
456 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
457 str = strtolower(str);
462 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
463 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
464 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
465 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
466 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
467 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
468 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
469 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
470 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
471 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
474 FOREACH(Scores, true, {
475 if (str == strtolower(scores_label(it))) {
477 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
487 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
491 sbt_field[sbt_num_fields] = j;
494 if(j == ps_secondary)
495 have_secondary = true;
500 if(sbt_num_fields >= MAX_SBT_FIELDS)
504 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
506 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
507 have_secondary = true;
508 if(ps_primary == ps_secondary)
509 have_secondary = true;
510 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
512 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
516 strunzone(sbt_field_title[sbt_num_fields]);
517 for(i = sbt_num_fields; i > 0; --i)
519 sbt_field_title[i] = sbt_field_title[i-1];
520 sbt_field_size[i] = sbt_field_size[i-1];
521 sbt_field[i] = sbt_field[i-1];
523 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
524 sbt_field[0] = SP_NAME;
526 LOG_INFO("fixed missing field 'name'");
530 strunzone(sbt_field_title[sbt_num_fields]);
531 for(i = sbt_num_fields; i > 1; --i)
533 sbt_field_title[i] = sbt_field_title[i-1];
534 sbt_field_size[i] = sbt_field_size[i-1];
535 sbt_field[i] = sbt_field[i-1];
537 sbt_field_title[1] = strzone("|");
538 sbt_field[1] = SP_SEPARATOR;
539 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
541 LOG_INFO("fixed missing field '|'");
544 else if(!have_separator)
546 strcpy(sbt_field_title[sbt_num_fields], "|");
547 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
548 sbt_field[sbt_num_fields] = SP_SEPARATOR;
550 LOG_INFO("fixed missing field '|'");
554 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
555 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
556 sbt_field[sbt_num_fields] = ps_secondary;
558 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
562 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
563 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
564 sbt_field[sbt_num_fields] = ps_primary;
566 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
570 sbt_field[sbt_num_fields] = SP_END;
574 vector sbt_field_rgb;
575 string sbt_field_icon0;
576 string sbt_field_icon1;
577 string sbt_field_icon2;
578 vector sbt_field_icon0_rgb;
579 vector sbt_field_icon1_rgb;
580 vector sbt_field_icon2_rgb;
581 string Scoreboard_GetName(entity pl)
583 if(ready_waiting && pl.ready)
585 sbt_field_icon0 = "gfx/scoreboard/player_ready";
589 int f = entcs_GetClientColors(pl.sv_entnum);
591 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
592 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
593 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
594 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
595 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
598 return entcs_GetName(pl.sv_entnum);
601 string Scoreboard_GetField(entity pl, PlayerScoreField field)
603 float tmp, num, denom;
606 sbt_field_rgb = '1 1 1';
607 sbt_field_icon0 = "";
608 sbt_field_icon1 = "";
609 sbt_field_icon2 = "";
610 sbt_field_icon0_rgb = '1 1 1';
611 sbt_field_icon1_rgb = '1 1 1';
612 sbt_field_icon2_rgb = '1 1 1';
617 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
618 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
622 tmp = max(0, min(220, f-80)) / 220;
623 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
629 f = pl.ping_packetloss;
630 tmp = pl.ping_movementloss;
631 if(f == 0 && tmp == 0)
633 str = ftos(ceil(f * 100));
635 str = strcat(str, "~", ftos(ceil(tmp * 100)));
636 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
637 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
641 return Scoreboard_GetName(pl);
644 f = pl.(scores(SP_KILLS));
645 f -= pl.(scores(SP_SUICIDES));
649 num = pl.(scores(SP_KILLS));
650 denom = pl.(scores(SP_DEATHS));
653 sbt_field_rgb = '0 1 0';
654 str = sprintf("%d", num);
655 } else if(num <= 0) {
656 sbt_field_rgb = '1 0 0';
657 str = sprintf("%.1f", num/denom);
659 str = sprintf("%.1f", num/denom);
663 f = pl.(scores(SP_KILLS));
664 f -= pl.(scores(SP_DEATHS));
667 sbt_field_rgb = '0 1 0';
669 sbt_field_rgb = '1 1 1';
671 sbt_field_rgb = '1 0 0';
677 float elo = pl.(scores(SP_ELO));
679 case -1: return "...";
680 case -2: return _("N/A");
681 default: return ftos(elo);
687 float fps = pl.(scores(SP_FPS));
690 sbt_field_rgb = '1 1 1';
691 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
693 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
694 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
698 case SP_DMG: case SP_DMGTAKEN:
699 return sprintf("%.1f k", pl.(scores(field)) / 1000);
701 default: case SP_SCORE:
702 tmp = pl.(scores(field));
703 f = scores_flags(field);
704 if(field == ps_primary)
705 sbt_field_rgb = '1 1 0';
706 else if(field == ps_secondary)
707 sbt_field_rgb = '0 1 1';
709 sbt_field_rgb = '1 1 1';
710 return ScoreString(f, tmp);
715 float sbt_fixcolumnwidth_len;
716 float sbt_fixcolumnwidth_iconlen;
717 float sbt_fixcolumnwidth_marginlen;
719 string Scoreboard_FixColumnWidth(int i, string str)
725 sbt_fixcolumnwidth_iconlen = 0;
727 if(sbt_field_icon0 != "")
729 sz = draw_getimagesize(sbt_field_icon0);
731 if(sbt_fixcolumnwidth_iconlen < f)
732 sbt_fixcolumnwidth_iconlen = f;
735 if(sbt_field_icon1 != "")
737 sz = draw_getimagesize(sbt_field_icon1);
739 if(sbt_fixcolumnwidth_iconlen < f)
740 sbt_fixcolumnwidth_iconlen = f;
743 if(sbt_field_icon2 != "")
745 sz = draw_getimagesize(sbt_field_icon2);
747 if(sbt_fixcolumnwidth_iconlen < f)
748 sbt_fixcolumnwidth_iconlen = f;
751 if(sbt_fixcolumnwidth_iconlen != 0)
753 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
754 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
757 sbt_fixcolumnwidth_marginlen = 0;
759 if(sbt_field[i] == SP_NAME) // name gets all remaining space
762 float remaining_space = 0;
763 for(j = 0; j < sbt_num_fields; ++j)
765 if (sbt_field[i] != SP_SEPARATOR)
766 remaining_space += sbt_field_size[j] + hud_fontsize.x;
767 sbt_field_size[i] = panel_size.x - remaining_space;
769 if (sbt_fixcolumnwidth_iconlen != 0)
770 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
771 float namesize = panel_size.x - remaining_space;
772 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
773 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
775 max_namesize = vid_conwidth - remaining_space;
778 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
780 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
781 if(sbt_field_size[i] < f)
782 sbt_field_size[i] = f;
787 void Scoreboard_initFieldSizes()
789 for(int i = 0; i < sbt_num_fields; ++i)
791 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
792 Scoreboard_FixColumnWidth(i, "");
796 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
799 vector column_dim = eY * panel_size.y;
801 column_dim.y -= 1.25 * hud_fontsize.y;
802 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
803 pos.x += hud_fontsize.x * 0.5;
804 for(i = 0; i < sbt_num_fields; ++i)
806 if(sbt_field[i] == SP_SEPARATOR)
808 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
811 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
812 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
813 pos.x += column_dim.x;
815 if(sbt_field[i] == SP_SEPARATOR)
817 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
818 for(i = sbt_num_fields - 1; i > 0; --i)
820 if(sbt_field[i] == SP_SEPARATOR)
823 pos.x -= sbt_field_size[i];
828 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
829 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
832 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
833 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
834 pos.x -= hud_fontsize.x;
839 pos.y += 1.25 * hud_fontsize.y;
843 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
845 TC(bool, is_self); TC(int, pl_number);
847 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
849 vector h_pos = item_pos;
850 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
851 // alternated rows highlighting
853 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
854 else if((sbt_highlight) && (!(pl_number % 2)))
855 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
857 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
859 vector pos = item_pos;
860 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
862 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);
864 pos.x += hud_fontsize.x * 0.5;
865 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
866 vector tmp = '0 0 0';
868 PlayerScoreField field;
869 for(i = 0; i < sbt_num_fields; ++i)
871 field = sbt_field[i];
872 if(field == SP_SEPARATOR)
875 if(is_spec && field != SP_NAME && field != SP_PING) {
876 pos.x += sbt_field_size[i] + hud_fontsize.x;
879 str = Scoreboard_GetField(pl, field);
880 str = Scoreboard_FixColumnWidth(i, str);
882 pos.x += sbt_field_size[i] + hud_fontsize.x;
884 if(field == SP_NAME) {
885 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
886 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
888 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
889 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
892 tmp.x = sbt_field_size[i] + hud_fontsize.x;
893 if(sbt_field_icon0 != "")
894 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
895 if(sbt_field_icon1 != "")
896 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
897 if(sbt_field_icon2 != "")
898 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
901 if(sbt_field[i] == SP_SEPARATOR)
903 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
904 for(i = sbt_num_fields-1; i > 0; --i)
906 field = sbt_field[i];
907 if(field == SP_SEPARATOR)
910 if(is_spec && field != SP_NAME && field != SP_PING) {
911 pos.x -= sbt_field_size[i] + hud_fontsize.x;
915 str = Scoreboard_GetField(pl, field);
916 str = Scoreboard_FixColumnWidth(i, str);
918 if(field == SP_NAME) {
919 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
920 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
922 tmp.x = sbt_fixcolumnwidth_len;
923 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
926 tmp.x = sbt_field_size[i];
927 if(sbt_field_icon0 != "")
928 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
929 if(sbt_field_icon1 != "")
930 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
931 if(sbt_field_icon2 != "")
932 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
933 pos.x -= sbt_field_size[i] + hud_fontsize.x;
938 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
941 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
944 vector h_pos = item_pos;
945 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
947 bool complete = (this_team == NUM_SPECTATOR);
950 if((sbt_highlight) && (!(pl_number % 2)))
951 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
953 vector pos = item_pos;
954 pos.x += hud_fontsize.x * 0.5;
955 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
957 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
959 width_limit -= stringwidth("...", false, hud_fontsize);
960 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
961 static float max_name_width = 0;
964 float min_fieldsize = 0;
965 float fieldpadding = hud_fontsize.x * 0.25;
966 if(this_team == NUM_SPECTATOR)
968 if(autocvar_hud_panel_scoreboard_spectators_showping)
969 min_fieldsize = stringwidth("999", false, hud_fontsize);
971 else if(autocvar_hud_panel_scoreboard_others_showscore)
972 min_fieldsize = stringwidth("99", false, hud_fontsize);
973 for(i = 0; pl; pl = pl.sort_next)
975 if(pl.team != this_team)
981 if(this_team == NUM_SPECTATOR)
983 if(autocvar_hud_panel_scoreboard_spectators_showping)
984 field = Scoreboard_GetField(pl, SP_PING);
986 else if(autocvar_hud_panel_scoreboard_others_showscore)
987 field = Scoreboard_GetField(pl, SP_SCORE);
989 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
990 float column_width = stringwidth(str, true, hud_fontsize);
991 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
993 if(column_width > max_name_width)
994 max_name_width = column_width;
995 column_width = max_name_width;
999 fieldsize = stringwidth(field, false, hud_fontsize);
1000 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1003 if(pos.x + column_width > width_limit)
1008 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1013 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1014 pos.y += hud_fontsize.y * 1.25;
1018 vector name_pos = pos;
1019 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1020 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1021 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1024 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1025 h_size.y = hud_fontsize.y;
1026 vector field_pos = pos;
1027 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1028 field_pos.x += column_width - h_size.x;
1030 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1031 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1032 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1036 h_size.x = column_width + hud_fontsize.x * 0.25;
1037 h_size.y = hud_fontsize.y;
1038 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1040 pos.x += column_width;
1041 pos.x += hud_fontsize.x;
1043 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1046 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1048 int max_players = 999;
1049 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1051 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1054 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1055 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1056 height /= team_count;
1059 height -= panel_bg_padding * 2; // - padding
1060 max_players = floor(height / (hud_fontsize.y * 1.25));
1061 if(max_players <= 1)
1063 if(max_players == tm.team_size)
1068 entity me = playerslots[current_player];
1070 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1071 panel_size.y += panel_bg_padding * 2;
1074 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1075 if(panel.current_panel_bg != "0")
1076 end_pos.y += panel_bg_border * 2;
1078 if(panel_bg_padding)
1080 panel_pos += '1 1 0' * panel_bg_padding;
1081 panel_size -= '2 2 0' * panel_bg_padding;
1085 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1089 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1091 pos.y += 1.25 * hud_fontsize.y;
1094 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1096 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1099 // print header row and highlight columns
1100 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1102 // fill the table and draw the rows
1103 bool is_self = false;
1104 bool self_shown = false;
1106 for(pl = players.sort_next; pl; pl = pl.sort_next)
1108 if(pl.team != tm.team)
1110 if(i == max_players - 2 && pl != me)
1112 if(!self_shown && me.team == tm.team)
1114 Scoreboard_DrawItem(pos, rgb, me, true, i);
1116 pos.y += 1.25 * hud_fontsize.y;
1120 if(i >= max_players - 1)
1122 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1125 is_self = (pl.sv_entnum == current_player);
1126 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1129 pos.y += 1.25 * hud_fontsize.y;
1133 panel_size.x += panel_bg_padding * 2; // restore initial width
1137 bool Scoreboard_WouldDraw()
1139 if (MUTATOR_CALLHOOK(DrawScoreboard))
1141 else if (QuickMenu_IsOpened())
1143 else if (HUD_Radar_Clickable())
1145 else if (scoreboard_showscores)
1147 else if (intermission == 1)
1149 else if (intermission == 2)
1151 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS)
1152 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1156 else if (scoreboard_showscores_force)
1161 float average_accuracy;
1162 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1166 if (scoreboard_fade_alpha == 1)
1167 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1169 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1171 vector initial_pos = pos;
1173 WepSet weapons_stat = WepSet_GetFromStat();
1174 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1175 int disownedcnt = 0;
1177 FOREACH(Weapons, it != WEP_Null, {
1178 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1180 WepSet set = it.m_wepset;
1181 if(it.spawnflags & WEP_TYPE_OTHER)
1186 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1188 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1195 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1196 if (weapon_cnt <= 0) return pos;
1199 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1201 int columnns = ceil(weapon_cnt / rows);
1203 float weapon_height = 29;
1204 float height = hud_fontsize.y + weapon_height;
1206 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);
1207 pos.y += 1.25 * hud_fontsize.y;
1208 if(panel.current_panel_bg != "0")
1209 pos.y += panel_bg_border;
1212 panel_size.y = height * rows;
1213 panel_size.y += panel_bg_padding * 2;
1215 float panel_bg_alpha_save = panel_bg_alpha;
1216 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1218 panel_bg_alpha = panel_bg_alpha_save;
1220 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1221 if(panel.current_panel_bg != "0")
1222 end_pos.y += panel_bg_border * 2;
1224 if(panel_bg_padding)
1226 panel_pos += '1 1 0' * panel_bg_padding;
1227 panel_size -= '2 2 0' * panel_bg_padding;
1231 vector tmp = panel_size;
1233 float weapon_width = tmp.x / columnns / rows;
1236 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1240 // column highlighting
1241 for (int i = 0; i < columnns; ++i)
1243 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);
1246 for (int i = 0; i < rows; ++i)
1247 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1250 average_accuracy = 0;
1251 int weapons_with_stats = 0;
1253 pos.x += weapon_width / 2;
1255 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1258 Accuracy_LoadColors();
1260 float oldposx = pos.x;
1264 FOREACH(Weapons, it != WEP_Null, {
1265 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1267 WepSet set = it.m_wepset;
1268 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1270 if (it.spawnflags & WEP_TYPE_OTHER)
1274 if (weapon_stats >= 0)
1275 weapon_alpha = sbt_fg_alpha;
1277 weapon_alpha = 0.2 * sbt_fg_alpha;
1280 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1282 if (weapon_stats >= 0) {
1283 weapons_with_stats += 1;
1284 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1287 s = sprintf("%d%%", weapon_stats * 100);
1290 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1292 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1293 rgb = Accuracy_GetColor(weapon_stats);
1295 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1297 tmpos.x += weapon_width * rows;
1298 pos.x += weapon_width * rows;
1299 if (rows == 2 && column == columnns - 1) {
1307 if (weapons_with_stats)
1308 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1310 panel_size.x += panel_bg_padding * 2; // restore initial width
1312 if (scoreboard_acc_fade_alpha == 1)
1314 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1317 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1319 pos.x += hud_fontsize.x * 0.25;
1320 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1321 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1322 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1324 pos.y += hud_fontsize.y;
1329 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1330 float stat_secrets_found, stat_secrets_total;
1331 float stat_monsters_killed, stat_monsters_total;
1335 // get monster stats
1336 stat_monsters_killed = STAT(MONSTERS_KILLED);
1337 stat_monsters_total = STAT(MONSTERS_TOTAL);
1339 // get secrets stats
1340 stat_secrets_found = STAT(SECRETS_FOUND);
1341 stat_secrets_total = STAT(SECRETS_TOTAL);
1343 // get number of rows
1344 if(stat_secrets_total)
1346 if(stat_monsters_total)
1349 // if no rows, return
1353 // draw table header
1354 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1355 pos.y += 1.25 * hud_fontsize.y;
1356 if(panel.current_panel_bg != "0")
1357 pos.y += panel_bg_border;
1360 panel_size.y = hud_fontsize.y * rows;
1361 panel_size.y += panel_bg_padding * 2;
1364 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1365 if(panel.current_panel_bg != "0")
1366 end_pos.y += panel_bg_border * 2;
1368 if(panel_bg_padding)
1370 panel_pos += '1 1 0' * panel_bg_padding;
1371 panel_size -= '2 2 0' * panel_bg_padding;
1375 vector tmp = panel_size;
1378 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1381 if(stat_monsters_total)
1383 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1384 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1388 if(stat_secrets_total)
1390 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1391 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1394 panel_size.x += panel_bg_padding * 2; // restore initial width
1399 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1402 RANKINGS_RECEIVED_CNT = 0;
1403 for (i=RANKINGS_CNT-1; i>=0; --i)
1405 ++RANKINGS_RECEIVED_CNT;
1407 if (RANKINGS_RECEIVED_CNT == 0)
1410 vector hl_rgb = rgb + '0.5 0.5 0.5';
1412 pos.y += hud_fontsize.y;
1413 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1414 pos.y += 1.25 * hud_fontsize.y;
1415 if(panel.current_panel_bg != "0")
1416 pos.y += panel_bg_border;
1421 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1423 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1428 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1430 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1434 float ranksize = 3 * hud_fontsize.x;
1435 float timesize = 5 * hud_fontsize.x;
1436 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1437 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1438 columns = min(columns, RANKINGS_RECEIVED_CNT);
1440 // expand name column to fill the entire row
1441 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1442 namesize += available_space;
1443 columnsize.x += available_space;
1445 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1446 panel_size.y += panel_bg_padding * 2;
1450 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1451 if(panel.current_panel_bg != "0")
1452 end_pos.y += panel_bg_border * 2;
1454 if(panel_bg_padding)
1456 panel_pos += '1 1 0' * panel_bg_padding;
1457 panel_size -= '2 2 0' * panel_bg_padding;
1463 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1465 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1467 int column = 0, j = 0;
1468 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1469 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1476 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1477 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1478 else if(!((j + column) & 1) && sbt_highlight)
1479 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1481 str = count_ordinal(i+1);
1482 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1483 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1484 str = ColorTranslateRGB(grecordholder[i]);
1486 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1487 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1489 pos.y += 1.25 * hud_fontsize.y;
1491 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1495 pos.x += panel_size.x / columns;
1496 pos.y = panel_pos.y;
1499 strfree(zoned_name_self);
1501 panel_size.x += panel_bg_padding * 2; // restore initial width
1505 float scoreboard_time;
1506 bool have_weapon_stats;
1507 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1509 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1511 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1514 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1515 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1521 if (!have_weapon_stats)
1523 FOREACH(Weapons, it != WEP_Null, {
1524 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1525 if (weapon_stats >= 0)
1527 have_weapon_stats = true;
1531 if (!have_weapon_stats)
1538 void Scoreboard_Draw()
1540 if(!autocvar__hud_configure)
1542 if(!hud_draw_maximized) return;
1544 // frametime checks allow to toggle the scoreboard even when the game is paused
1545 if(scoreboard_active) {
1546 if (scoreboard_fade_alpha < 1)
1547 scoreboard_time = time;
1548 if(hud_configure_menu_open == 1)
1549 scoreboard_fade_alpha = 1;
1550 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1551 if (scoreboard_fadeinspeed && frametime)
1552 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1554 scoreboard_fade_alpha = 1;
1555 if(hud_fontsize_str != autocvar_hud_fontsize)
1557 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1558 Scoreboard_initFieldSizes();
1559 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1563 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1564 if (scoreboard_fadeoutspeed && frametime)
1565 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1567 scoreboard_fade_alpha = 0;
1570 if (!scoreboard_fade_alpha)
1572 scoreboard_acc_fade_alpha = 0;
1577 scoreboard_fade_alpha = 0;
1579 if (autocvar_hud_panel_scoreboard_dynamichud)
1582 HUD_Scale_Disable();
1584 if(scoreboard_fade_alpha <= 0)
1586 panel_fade_alpha *= scoreboard_fade_alpha;
1587 HUD_Panel_LoadCvars();
1589 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1590 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1591 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1592 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1593 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1594 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1596 // don't overlap with con_notify
1597 if(!autocvar__hud_configure)
1598 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1600 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1601 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1602 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1603 panel_size.x = fixed_scoreboard_width;
1605 Scoreboard_UpdatePlayerTeams();
1607 vector pos = panel_pos;
1612 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1614 // Begin of Game Info Section
1615 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1616 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1618 // Game Info: Game Type
1619 str = MapInfo_Type_ToText(gametype);
1620 draw_beginBoldFont();
1621 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);
1624 // Game Info: Game Detail
1625 float tl = STAT(TIMELIMIT);
1626 float fl = STAT(FRAGLIMIT);
1627 float ll = STAT(LEADLIMIT);
1628 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1631 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1632 if(!ISGAMETYPE(LMS))
1637 str = strcat(str, "^7 / "); // delimiter
1640 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1641 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1642 (teamscores_label(ts_primary) == "fastest") ? "" :
1643 TranslateScoresLabel(teamscores_label(ts_primary))));
1647 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1648 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1649 (scores_label(ps_primary) == "fastest") ? "" :
1650 TranslateScoresLabel(scores_label(ps_primary))));
1655 if(tl > 0 || fl > 0)
1658 if (ll_and_fl && fl > 0)
1659 str = strcat(str, "^7 & ");
1661 str = strcat(str, "^7 / ");
1666 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1667 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1668 (teamscores_label(ts_primary) == "fastest") ? "" :
1669 TranslateScoresLabel(teamscores_label(ts_primary))));
1673 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1674 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1675 (scores_label(ps_primary) == "fastest") ? "" :
1676 TranslateScoresLabel(scores_label(ps_primary))));
1681 pos.y += sb_gameinfo_type_fontsize.y;
1682 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
1684 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1685 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1686 // End of Game Info Section
1688 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1689 if(panel.current_panel_bg != "0")
1690 pos.y += panel_bg_border;
1692 // Draw the scoreboard
1693 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1696 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1700 vector panel_bg_color_save = panel_bg_color;
1701 vector team_score_baseoffset;
1702 vector team_size_baseoffset;
1703 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1705 // put team score to the left of scoreboard (and team size to the right)
1706 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1707 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1708 if(panel.current_panel_bg != "0")
1710 team_score_baseoffset.x -= panel_bg_border;
1711 team_size_baseoffset.x += panel_bg_border;
1716 // put team score to the right of scoreboard (and team size to the left)
1717 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1718 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1719 if(panel.current_panel_bg != "0")
1721 team_score_baseoffset.x += panel_bg_border;
1722 team_size_baseoffset.x -= panel_bg_border;
1726 int team_size_total = 0;
1727 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1729 // calculate team size total (sum of all team sizes)
1730 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1731 if(tm.team != NUM_SPECTATOR)
1732 team_size_total += tm.team_size;
1735 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1737 if(tm.team == NUM_SPECTATOR)
1742 draw_beginBoldFont();
1743 vector rgb = Team_ColorRGB(tm.team);
1744 str = ftos(tm.(teamscores(ts_primary)));
1745 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1747 // team score on the left (default)
1748 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1752 // team score on the right
1753 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1755 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1757 // team size (if set to show on the side)
1758 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1760 // calculate the starting position for the whole team size info string
1761 str = sprintf("%d/%d", tm.team_size, team_size_total);
1762 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1764 // team size on the left
1765 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1769 // team size on the right
1770 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1772 str = sprintf("%d", tm.team_size);
1773 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1774 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1775 str = sprintf("/%d", team_size_total);
1776 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1780 // secondary score, e.g. keyhunt
1781 if(ts_primary != ts_secondary)
1783 str = ftos(tm.(teamscores(ts_secondary)));
1784 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1787 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1792 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1795 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1798 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1799 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1800 else if(panel_bg_color_team > 0)
1801 panel_bg_color = rgb * panel_bg_color_team;
1803 panel_bg_color = rgb;
1804 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1806 panel_bg_color = panel_bg_color_save;
1810 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1811 if(tm.team != NUM_SPECTATOR)
1814 // display it anyway
1815 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1818 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1819 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1821 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1822 if(race_speedaward) {
1823 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1824 pos.y += 1.25 * hud_fontsize.y;
1826 if(race_speedaward_alltimebest) {
1827 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1828 pos.y += 1.25 * hud_fontsize.y;
1830 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1833 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1836 for(pl = players.sort_next; pl; pl = pl.sort_next)
1838 if(pl.team == NUM_SPECTATOR)
1840 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1841 if(tm.team == NUM_SPECTATOR)
1843 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1844 draw_beginBoldFont();
1845 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1847 pos.y += 1.25 * hud_fontsize.y;
1849 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1850 pos.y += 1.25 * hud_fontsize.y;
1857 // print information about respawn status
1858 float respawn_time = STAT(RESPAWN_TIME);
1862 if(respawn_time < 0)
1864 // a negative number means we are awaiting respawn, time value is still the same
1865 respawn_time *= -1; // remove mark now that we checked it
1867 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1868 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1870 str = sprintf(_("^1Respawning in ^3%s^1..."),
1871 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1872 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1874 count_seconds(ceil(respawn_time - time))
1878 else if(time < respawn_time)
1880 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1881 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1882 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1884 count_seconds(ceil(respawn_time - time))
1888 else if(time >= respawn_time)
1889 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1891 pos.y += 1.2 * hud_fontsize.y;
1892 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1895 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;