1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 void Scoreboard_Draw_Export(int fh)
21 // allow saving cvars that aesthetically change the panel into hud skin files
22 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
25 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
33 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 const int MAX_SBT_FIELDS = MAX_SCORE;
40 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
41 float sbt_field_size[MAX_SBT_FIELDS + 1];
42 string sbt_field_title[MAX_SBT_FIELDS + 1];
45 string autocvar_hud_fontsize;
46 string hud_fontsize_str;
51 float sbt_fg_alpha_self;
53 float sbt_highlight_alpha;
54 float sbt_highlight_alpha_self;
55 float sbt_highlight_alpha_eliminated;
57 // provide basic panel cvars to old clients
58 // TODO remove them after a future release (0.8.2+)
59 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
60 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
61 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
62 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
63 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
64 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
65 noref string autocvar_hud_panel_scoreboard_bg_border = "";
66 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
68 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
69 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
70 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
71 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
72 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
73 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
75 bool autocvar_hud_panel_scoreboard_table_highlight = true;
76 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
79 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
80 float autocvar_hud_panel_scoreboard_namesize = 15;
81 float autocvar_hud_panel_scoreboard_team_size_position = 0;
83 bool autocvar_hud_panel_scoreboard_accuracy = true;
84 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
85 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
86 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
89 bool autocvar_hud_panel_scoreboard_dynamichud = false;
91 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
92 bool autocvar_hud_panel_scoreboard_others_showscore = true;
93 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
94 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
95 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
96 bool autocvar_hud_panel_scoreboard_playerid = false;
97 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
98 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
100 // mode 0: returns translated label
101 // mode 1: prints name and description of all the labels
102 string Label_getInfo(string label, int mode)
105 label = "bckills"; // first case in the switch
109 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
110 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
111 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")));
112 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
113 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
114 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
115 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
116 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
117 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
118 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
119 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
120 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
121 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
122 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
123 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
124 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
125 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
126 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
127 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
128 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
129 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
130 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
131 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
132 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
133 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
134 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
135 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
136 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")));
137 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
138 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
139 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
140 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
141 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
142 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
143 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
144 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
145 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
146 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
147 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
148 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
149 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
150 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
151 default: return label;
156 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
157 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
159 void Scoreboard_InitScores()
163 ps_primary = ps_secondary = NULL;
164 ts_primary = ts_secondary = -1;
165 FOREACH(Scores, true, {
166 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
167 if(f == SFL_SORT_PRIO_PRIMARY)
169 if(f == SFL_SORT_PRIO_SECONDARY)
172 if(ps_secondary == NULL)
173 ps_secondary = ps_primary;
175 for(i = 0; i < MAX_TEAMSCORE; ++i)
177 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
178 if(f == SFL_SORT_PRIO_PRIMARY)
180 if(f == SFL_SORT_PRIO_SECONDARY)
183 if(ts_secondary == -1)
184 ts_secondary = ts_primary;
186 Cmd_Scoreboard_SetFields(0);
190 void Scoreboard_UpdatePlayerTeams()
194 for(pl = players.sort_next; pl; pl = pl.sort_next)
197 int Team = entcs_GetScoreTeam(pl.sv_entnum);
198 if(SetTeam(pl, Team))
201 Scoreboard_UpdatePlayerPos(pl);
205 pl = players.sort_next;
210 print(strcat("PNUM: ", ftos(num), "\n"));
215 int Scoreboard_CompareScore(int vl, int vr, int f)
217 TC(int, vl); TC(int, vr); TC(int, f);
218 if(f & SFL_ZERO_IS_WORST)
220 if(vl == 0 && vr != 0)
222 if(vl != 0 && vr == 0)
226 return IS_INCREASING(f);
228 return IS_DECREASING(f);
232 float Scoreboard_ComparePlayerScores(entity left, entity right)
235 vl = entcs_GetTeam(left.sv_entnum);
236 vr = entcs_GetTeam(right.sv_entnum);
248 if(vl == NUM_SPECTATOR)
250 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
252 if(!left.gotscores && right.gotscores)
257 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
261 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
265 FOREACH(Scores, true, {
266 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
267 if (r >= 0) return r;
270 if (left.sv_entnum < right.sv_entnum)
276 void Scoreboard_UpdatePlayerPos(entity player)
279 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
281 SORT_SWAP(player, ent);
283 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
285 SORT_SWAP(ent, player);
289 float Scoreboard_CompareTeamScores(entity left, entity right)
293 if(left.team == NUM_SPECTATOR)
295 if(right.team == NUM_SPECTATOR)
298 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
302 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
306 for(i = 0; i < MAX_TEAMSCORE; ++i)
308 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
313 if (left.team < right.team)
319 void Scoreboard_UpdateTeamPos(entity Team)
322 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
324 SORT_SWAP(Team, ent);
326 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
328 SORT_SWAP(ent, Team);
332 void Cmd_Scoreboard_Help()
334 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
335 LOG_HELP(_("Usage:"));
336 LOG_HELP("^2scoreboard_columns_set ^3default");
337 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
338 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
339 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
340 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
341 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
342 LOG_HELP(_("The following field names are recognized (case insensitive):"));
348 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
349 "of game types, then a slash, to make the field show up only in these\n"
350 "or in all but these game types. You can also specify 'all' as a\n"
351 "field to show all fields available for the current game mode."));
354 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
355 "include/exclude ALL teams/noteams game modes."));
358 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
359 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
360 "right of the vertical bar aligned to the right."));
361 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
362 "other gamemodes except DM."));
365 // NOTE: adding a gametype with ? to not warn for an optional field
366 // make sure it's excluded in a previous exclusive rule, if any
367 // otherwise the previous exclusive rule warns anyway
368 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
369 #define SCOREBOARD_DEFAULT_COLUMNS \
370 "ping pl fps name |" \
371 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
372 " -teams,lms/deaths +ft,tdm/deaths" \
374 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
375 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
376 " +tdm,ft,dom,ons,as/teamkills"\
377 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
378 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
379 " +lms/lives +lms/rank" \
380 " +kh/kckills +kh/losses +kh/caps" \
381 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
382 " +as/objectives +nb/faults +nb/goals" \
383 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
384 " +dom/ticks +dom/takes" \
385 " -lms,rc,cts,inv,nb/score"
387 void Cmd_Scoreboard_SetFields(int argc)
392 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
396 return; // do nothing, we don't know gametype and scores yet
398 // sbt_fields uses strunzone on the titles!
399 if(!sbt_field_title[0])
400 for(i = 0; i < MAX_SBT_FIELDS; ++i)
401 sbt_field_title[i] = strzone("(null)");
403 // TODO: re enable with gametype dependant cvars?
404 if(argc < 3) // no arguments provided
405 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
408 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
412 if(argv(2) == "default" || argv(2) == "expand_default")
414 if(argv(2) == "expand_default")
415 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
416 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
418 else if(argv(2) == "all")
420 string s = "ping pl name |"; // scores without a label
421 FOREACH(Scores, true, {
423 if(it != ps_secondary)
424 if(scores_label(it) != "")
425 s = strcat(s, " ", scores_label(it));
427 if(ps_secondary != ps_primary)
428 s = strcat(s, " ", scores_label(ps_secondary));
429 s = strcat(s, " ", scores_label(ps_primary));
430 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
437 hud_fontsize = HUD_GetFontsize("hud_fontsize");
439 for(i = 1; i < argc - 1; ++i)
442 bool nocomplain = false;
443 if(substring(str, 0, 1) == "?")
446 str = substring(str, 1, strlen(str) - 1);
449 slash = strstrofs(str, "/", 0);
452 pattern = substring(str, 0, slash);
453 str = substring(str, slash + 1, strlen(str) - (slash + 1));
455 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
459 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
460 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
461 str = strtolower(str);
466 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
467 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
468 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
469 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
470 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
471 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
472 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
473 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
474 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
475 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
478 FOREACH(Scores, true, {
479 if (str == strtolower(scores_label(it))) {
481 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
491 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
495 sbt_field[sbt_num_fields] = j;
498 if(j == ps_secondary)
499 have_secondary = true;
504 if(sbt_num_fields >= MAX_SBT_FIELDS)
508 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
510 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
511 have_secondary = true;
512 if(ps_primary == ps_secondary)
513 have_secondary = true;
514 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
516 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
520 strunzone(sbt_field_title[sbt_num_fields]);
521 for(i = sbt_num_fields; i > 0; --i)
523 sbt_field_title[i] = sbt_field_title[i-1];
524 sbt_field_size[i] = sbt_field_size[i-1];
525 sbt_field[i] = sbt_field[i-1];
527 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
528 sbt_field[0] = SP_NAME;
530 LOG_INFO("fixed missing field 'name'");
534 strunzone(sbt_field_title[sbt_num_fields]);
535 for(i = sbt_num_fields; i > 1; --i)
537 sbt_field_title[i] = sbt_field_title[i-1];
538 sbt_field_size[i] = sbt_field_size[i-1];
539 sbt_field[i] = sbt_field[i-1];
541 sbt_field_title[1] = strzone("|");
542 sbt_field[1] = SP_SEPARATOR;
543 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
545 LOG_INFO("fixed missing field '|'");
548 else if(!have_separator)
550 strcpy(sbt_field_title[sbt_num_fields], "|");
551 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
552 sbt_field[sbt_num_fields] = SP_SEPARATOR;
554 LOG_INFO("fixed missing field '|'");
558 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
559 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
560 sbt_field[sbt_num_fields] = ps_secondary;
562 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
566 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
567 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
568 sbt_field[sbt_num_fields] = ps_primary;
570 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
574 sbt_field[sbt_num_fields] = SP_END;
577 string Scoreboard_AddPlayerId(string pl_name, entity pl)
579 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
580 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
581 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
585 vector sbt_field_rgb;
586 string sbt_field_icon0;
587 string sbt_field_icon1;
588 string sbt_field_icon2;
589 vector sbt_field_icon0_rgb;
590 vector sbt_field_icon1_rgb;
591 vector sbt_field_icon2_rgb;
592 string Scoreboard_GetName(entity pl)
594 if(ready_waiting && pl.ready)
596 sbt_field_icon0 = "gfx/scoreboard/player_ready";
600 int f = entcs_GetClientColors(pl.sv_entnum);
602 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
603 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
604 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
605 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
606 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
609 return entcs_GetName(pl.sv_entnum);
612 string Scoreboard_GetField(entity pl, PlayerScoreField field)
614 float tmp, num, denom;
617 sbt_field_rgb = '1 1 1';
618 sbt_field_icon0 = "";
619 sbt_field_icon1 = "";
620 sbt_field_icon2 = "";
621 sbt_field_icon0_rgb = '1 1 1';
622 sbt_field_icon1_rgb = '1 1 1';
623 sbt_field_icon2_rgb = '1 1 1';
628 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
629 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
633 tmp = max(0, min(220, f-80)) / 220;
634 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
640 f = pl.ping_packetloss;
641 tmp = pl.ping_movementloss;
642 if(f == 0 && tmp == 0)
644 str = ftos(ceil(f * 100));
646 str = strcat(str, "~", ftos(ceil(tmp * 100)));
647 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
648 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
652 str = Scoreboard_GetName(pl);
653 if (autocvar_hud_panel_scoreboard_playerid)
654 str = Scoreboard_AddPlayerId(str, pl);
658 f = pl.(scores(SP_KILLS));
659 f -= pl.(scores(SP_SUICIDES));
663 num = pl.(scores(SP_KILLS));
664 denom = pl.(scores(SP_DEATHS));
667 sbt_field_rgb = '0 1 0';
668 str = sprintf("%d", num);
669 } else if(num <= 0) {
670 sbt_field_rgb = '1 0 0';
671 str = sprintf("%.1f", num/denom);
673 str = sprintf("%.1f", num/denom);
677 f = pl.(scores(SP_KILLS));
678 f -= pl.(scores(SP_DEATHS));
681 sbt_field_rgb = '0 1 0';
683 sbt_field_rgb = '1 1 1';
685 sbt_field_rgb = '1 0 0';
691 float elo = pl.(scores(SP_ELO));
693 case -1: return "...";
694 case -2: return _("N/A");
695 default: return ftos(elo);
701 float fps = pl.(scores(SP_FPS));
704 sbt_field_rgb = '1 1 1';
705 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
707 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
708 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
712 case SP_DMG: case SP_DMGTAKEN:
713 return sprintf("%.1f k", pl.(scores(field)) / 1000);
715 default: case SP_SCORE:
716 tmp = pl.(scores(field));
717 f = scores_flags(field);
718 if(field == ps_primary)
719 sbt_field_rgb = '1 1 0';
720 else if(field == ps_secondary)
721 sbt_field_rgb = '0 1 1';
723 sbt_field_rgb = '1 1 1';
724 return ScoreString(f, tmp);
729 float sbt_fixcolumnwidth_len;
730 float sbt_fixcolumnwidth_iconlen;
731 float sbt_fixcolumnwidth_marginlen;
733 string Scoreboard_FixColumnWidth(int i, string str)
739 sbt_fixcolumnwidth_iconlen = 0;
741 if(sbt_field_icon0 != "")
743 sz = draw_getimagesize(sbt_field_icon0);
745 if(sbt_fixcolumnwidth_iconlen < f)
746 sbt_fixcolumnwidth_iconlen = f;
749 if(sbt_field_icon1 != "")
751 sz = draw_getimagesize(sbt_field_icon1);
753 if(sbt_fixcolumnwidth_iconlen < f)
754 sbt_fixcolumnwidth_iconlen = f;
757 if(sbt_field_icon2 != "")
759 sz = draw_getimagesize(sbt_field_icon2);
761 if(sbt_fixcolumnwidth_iconlen < f)
762 sbt_fixcolumnwidth_iconlen = f;
765 if(sbt_fixcolumnwidth_iconlen != 0)
767 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
768 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
771 sbt_fixcolumnwidth_marginlen = 0;
773 if(sbt_field[i] == SP_NAME) // name gets all remaining space
776 float remaining_space = 0;
777 for(j = 0; j < sbt_num_fields; ++j)
779 if (sbt_field[i] != SP_SEPARATOR)
780 remaining_space += sbt_field_size[j] + hud_fontsize.x;
781 sbt_field_size[i] = panel_size.x - remaining_space;
783 if (sbt_fixcolumnwidth_iconlen != 0)
784 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
785 float namesize = panel_size.x - remaining_space;
786 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
787 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
789 max_namesize = vid_conwidth - remaining_space;
792 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
794 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
795 if(sbt_field_size[i] < f)
796 sbt_field_size[i] = f;
801 void Scoreboard_initFieldSizes()
803 for(int i = 0; i < sbt_num_fields; ++i)
805 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
806 Scoreboard_FixColumnWidth(i, "");
810 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
813 vector column_dim = eY * panel_size.y;
815 column_dim.y -= 1.25 * hud_fontsize.y;
816 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
817 pos.x += hud_fontsize.x * 0.5;
818 for(i = 0; i < sbt_num_fields; ++i)
820 if(sbt_field[i] == SP_SEPARATOR)
822 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
825 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
826 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
827 pos.x += column_dim.x;
829 if(sbt_field[i] == SP_SEPARATOR)
831 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
832 for(i = sbt_num_fields - 1; i > 0; --i)
834 if(sbt_field[i] == SP_SEPARATOR)
837 pos.x -= sbt_field_size[i];
842 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
843 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
846 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
847 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
848 pos.x -= hud_fontsize.x;
853 pos.y += 1.25 * hud_fontsize.y;
857 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
859 TC(bool, is_self); TC(int, pl_number);
861 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
863 vector h_pos = item_pos;
864 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
865 // alternated rows highlighting
867 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
868 else if((sbt_highlight) && (!(pl_number % 2)))
869 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
871 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
873 vector pos = item_pos;
874 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
876 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);
878 pos.x += hud_fontsize.x * 0.5;
879 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
880 vector tmp = '0 0 0';
882 PlayerScoreField field;
883 for(i = 0; i < sbt_num_fields; ++i)
885 field = sbt_field[i];
886 if(field == SP_SEPARATOR)
889 if(is_spec && field != SP_NAME && field != SP_PING) {
890 pos.x += sbt_field_size[i] + hud_fontsize.x;
893 str = Scoreboard_GetField(pl, field);
894 str = Scoreboard_FixColumnWidth(i, str);
896 pos.x += sbt_field_size[i] + hud_fontsize.x;
898 if(field == SP_NAME) {
899 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
900 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
903 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 tmp.x = sbt_field_size[i] + hud_fontsize.x;
907 if(sbt_field_icon0 != "")
908 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 if(sbt_field_icon1 != "")
910 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
911 if(sbt_field_icon2 != "")
912 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
915 if(sbt_field[i] == SP_SEPARATOR)
917 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
918 for(i = sbt_num_fields-1; i > 0; --i)
920 field = sbt_field[i];
921 if(field == SP_SEPARATOR)
924 if(is_spec && field != SP_NAME && field != SP_PING) {
925 pos.x -= sbt_field_size[i] + hud_fontsize.x;
929 str = Scoreboard_GetField(pl, field);
930 str = Scoreboard_FixColumnWidth(i, str);
932 if(field == SP_NAME) {
933 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
934 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
936 tmp.x = sbt_fixcolumnwidth_len;
937 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
940 tmp.x = sbt_field_size[i];
941 if(sbt_field_icon0 != "")
942 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
943 if(sbt_field_icon1 != "")
944 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
945 if(sbt_field_icon2 != "")
946 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 pos.x -= sbt_field_size[i] + hud_fontsize.x;
952 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
955 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
958 vector h_pos = item_pos;
959 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
961 bool complete = (this_team == NUM_SPECTATOR);
964 if((sbt_highlight) && (!(pl_number % 2)))
965 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
967 vector pos = item_pos;
968 pos.x += hud_fontsize.x * 0.5;
969 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
971 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
973 width_limit -= stringwidth("...", false, hud_fontsize);
974 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
975 static float max_name_width = 0;
978 float min_fieldsize = 0;
979 float fieldpadding = hud_fontsize.x * 0.25;
980 if(this_team == NUM_SPECTATOR)
982 if(autocvar_hud_panel_scoreboard_spectators_showping)
983 min_fieldsize = stringwidth("999", false, hud_fontsize);
985 else if(autocvar_hud_panel_scoreboard_others_showscore)
986 min_fieldsize = stringwidth("99", false, hud_fontsize);
987 for(i = 0; pl; pl = pl.sort_next)
989 if(pl.team != this_team)
995 if(this_team == NUM_SPECTATOR)
997 if(autocvar_hud_panel_scoreboard_spectators_showping)
998 field = Scoreboard_GetField(pl, SP_PING);
1000 else if(autocvar_hud_panel_scoreboard_others_showscore)
1001 field = Scoreboard_GetField(pl, SP_SCORE);
1003 string str = entcs_GetName(pl.sv_entnum);
1004 if (autocvar_hud_panel_scoreboard_playerid)
1005 str = Scoreboard_AddPlayerId(str, pl);
1006 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1007 float column_width = stringwidth(str, true, hud_fontsize);
1008 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1010 if(column_width > max_name_width)
1011 max_name_width = column_width;
1012 column_width = max_name_width;
1016 fieldsize = stringwidth(field, false, hud_fontsize);
1017 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1020 if(pos.x + column_width > width_limit)
1025 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1030 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1031 pos.y += hud_fontsize.y * 1.25;
1035 vector name_pos = pos;
1036 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1037 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1038 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1041 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1042 h_size.y = hud_fontsize.y;
1043 vector field_pos = pos;
1044 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1045 field_pos.x += column_width - h_size.x;
1047 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1048 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1049 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1053 h_size.x = column_width + hud_fontsize.x * 0.25;
1054 h_size.y = hud_fontsize.y;
1055 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1057 pos.x += column_width;
1058 pos.x += hud_fontsize.x;
1060 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1063 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1065 int max_players = 999;
1066 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1068 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1071 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1072 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1073 height /= team_count;
1076 height -= panel_bg_padding * 2; // - padding
1077 max_players = floor(height / (hud_fontsize.y * 1.25));
1078 if(max_players <= 1)
1080 if(max_players == tm.team_size)
1085 entity me = playerslots[current_player];
1087 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1088 panel_size.y += panel_bg_padding * 2;
1091 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1092 if(panel.current_panel_bg != "0")
1093 end_pos.y += panel_bg_border * 2;
1095 if(panel_bg_padding)
1097 panel_pos += '1 1 0' * panel_bg_padding;
1098 panel_size -= '2 2 0' * panel_bg_padding;
1102 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1106 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1108 pos.y += 1.25 * hud_fontsize.y;
1111 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1113 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1116 // print header row and highlight columns
1117 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1119 // fill the table and draw the rows
1120 bool is_self = false;
1121 bool self_shown = false;
1123 for(pl = players.sort_next; pl; pl = pl.sort_next)
1125 if(pl.team != tm.team)
1127 if(i == max_players - 2 && pl != me)
1129 if(!self_shown && me.team == tm.team)
1131 Scoreboard_DrawItem(pos, rgb, me, true, i);
1133 pos.y += 1.25 * hud_fontsize.y;
1137 if(i >= max_players - 1)
1139 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1142 is_self = (pl.sv_entnum == current_player);
1143 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1146 pos.y += 1.25 * hud_fontsize.y;
1150 panel_size.x += panel_bg_padding * 2; // restore initial width
1154 bool Scoreboard_WouldDraw()
1156 if (MUTATOR_CALLHOOK(DrawScoreboard))
1158 else if (QuickMenu_IsOpened())
1160 else if (HUD_Radar_Clickable())
1162 else if (scoreboard_showscores)
1164 else if (intermission == 1)
1166 else if (intermission == 2)
1168 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1169 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1173 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1178 float average_accuracy;
1179 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1183 if (scoreboard_fade_alpha == 1)
1184 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1186 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1188 vector initial_pos = pos;
1190 WepSet weapons_stat = WepSet_GetFromStat();
1191 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1192 int disownedcnt = 0;
1194 FOREACH(Weapons, it != WEP_Null, {
1195 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1197 WepSet set = it.m_wepset;
1198 if(it.spawnflags & WEP_TYPE_OTHER)
1203 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1205 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1212 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1213 if (weapon_cnt <= 0) return pos;
1216 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1218 int columnns = ceil(weapon_cnt / rows);
1220 float weapon_height = 29;
1221 float height = hud_fontsize.y + weapon_height;
1223 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);
1224 pos.y += 1.25 * hud_fontsize.y;
1225 if(panel.current_panel_bg != "0")
1226 pos.y += panel_bg_border;
1229 panel_size.y = height * rows;
1230 panel_size.y += panel_bg_padding * 2;
1232 float panel_bg_alpha_save = panel_bg_alpha;
1233 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1235 panel_bg_alpha = panel_bg_alpha_save;
1237 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1238 if(panel.current_panel_bg != "0")
1239 end_pos.y += panel_bg_border * 2;
1241 if(panel_bg_padding)
1243 panel_pos += '1 1 0' * panel_bg_padding;
1244 panel_size -= '2 2 0' * panel_bg_padding;
1248 vector tmp = panel_size;
1250 float weapon_width = tmp.x / columnns / rows;
1253 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1257 // column highlighting
1258 for (int i = 0; i < columnns; ++i)
1260 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);
1263 for (int i = 0; i < rows; ++i)
1264 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1267 average_accuracy = 0;
1268 int weapons_with_stats = 0;
1270 pos.x += weapon_width / 2;
1272 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1275 Accuracy_LoadColors();
1277 float oldposx = pos.x;
1281 FOREACH(Weapons, it != WEP_Null, {
1282 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1284 WepSet set = it.m_wepset;
1285 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1287 if (it.spawnflags & WEP_TYPE_OTHER)
1291 if (weapon_stats >= 0)
1292 weapon_alpha = sbt_fg_alpha;
1294 weapon_alpha = 0.2 * sbt_fg_alpha;
1297 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1299 if (weapon_stats >= 0) {
1300 weapons_with_stats += 1;
1301 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1304 s = sprintf("%d%%", weapon_stats * 100);
1307 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1309 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1310 rgb = Accuracy_GetColor(weapon_stats);
1312 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1314 tmpos.x += weapon_width * rows;
1315 pos.x += weapon_width * rows;
1316 if (rows == 2 && column == columnns - 1) {
1324 if (weapons_with_stats)
1325 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1327 panel_size.x += panel_bg_padding * 2; // restore initial width
1329 if (scoreboard_acc_fade_alpha == 1)
1331 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1334 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1336 pos.x += hud_fontsize.x * 0.25;
1337 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1338 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1339 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1341 pos.y += hud_fontsize.y;
1346 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1347 float stat_secrets_found, stat_secrets_total;
1348 float stat_monsters_killed, stat_monsters_total;
1352 // get monster stats
1353 stat_monsters_killed = STAT(MONSTERS_KILLED);
1354 stat_monsters_total = STAT(MONSTERS_TOTAL);
1356 // get secrets stats
1357 stat_secrets_found = STAT(SECRETS_FOUND);
1358 stat_secrets_total = STAT(SECRETS_TOTAL);
1360 // get number of rows
1361 if(stat_secrets_total)
1363 if(stat_monsters_total)
1366 // if no rows, return
1370 // draw table header
1371 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1372 pos.y += 1.25 * hud_fontsize.y;
1373 if(panel.current_panel_bg != "0")
1374 pos.y += panel_bg_border;
1377 panel_size.y = hud_fontsize.y * rows;
1378 panel_size.y += panel_bg_padding * 2;
1381 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1382 if(panel.current_panel_bg != "0")
1383 end_pos.y += panel_bg_border * 2;
1385 if(panel_bg_padding)
1387 panel_pos += '1 1 0' * panel_bg_padding;
1388 panel_size -= '2 2 0' * panel_bg_padding;
1392 vector tmp = panel_size;
1395 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1398 if(stat_monsters_total)
1400 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1401 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1405 if(stat_secrets_total)
1407 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1408 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1411 panel_size.x += panel_bg_padding * 2; // restore initial width
1416 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1419 RANKINGS_RECEIVED_CNT = 0;
1420 for (i=RANKINGS_CNT-1; i>=0; --i)
1422 ++RANKINGS_RECEIVED_CNT;
1424 if (RANKINGS_RECEIVED_CNT == 0)
1427 vector hl_rgb = rgb + '0.5 0.5 0.5';
1429 pos.y += hud_fontsize.y;
1430 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1431 pos.y += 1.25 * hud_fontsize.y;
1432 if(panel.current_panel_bg != "0")
1433 pos.y += panel_bg_border;
1438 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1440 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1445 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1447 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1451 float ranksize = 3 * hud_fontsize.x;
1452 float timesize = 5 * hud_fontsize.x;
1453 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1454 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1455 columns = min(columns, RANKINGS_RECEIVED_CNT);
1457 // expand name column to fill the entire row
1458 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1459 namesize += available_space;
1460 columnsize.x += available_space;
1462 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1463 panel_size.y += panel_bg_padding * 2;
1467 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1468 if(panel.current_panel_bg != "0")
1469 end_pos.y += panel_bg_border * 2;
1471 if(panel_bg_padding)
1473 panel_pos += '1 1 0' * panel_bg_padding;
1474 panel_size -= '2 2 0' * panel_bg_padding;
1480 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1482 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1484 int column = 0, j = 0;
1485 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1486 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1493 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1494 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1495 else if(!((j + column) & 1) && sbt_highlight)
1496 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1498 str = count_ordinal(i+1);
1499 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1500 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1501 str = ColorTranslateRGB(grecordholder[i]);
1503 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1504 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1506 pos.y += 1.25 * hud_fontsize.y;
1508 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1512 pos.x += panel_size.x / columns;
1513 pos.y = panel_pos.y;
1516 strfree(zoned_name_self);
1518 panel_size.x += panel_bg_padding * 2; // restore initial width
1522 float scoreboard_time;
1523 bool have_weapon_stats;
1524 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1526 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1528 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1531 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1532 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1538 if (!have_weapon_stats)
1540 FOREACH(Weapons, it != WEP_Null, {
1541 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1542 if (weapon_stats >= 0)
1544 have_weapon_stats = true;
1548 if (!have_weapon_stats)
1555 void Scoreboard_Draw()
1557 if(!autocvar__hud_configure)
1559 if(!hud_draw_maximized) return;
1561 // frametime checks allow to toggle the scoreboard even when the game is paused
1562 if(scoreboard_active) {
1563 if (scoreboard_fade_alpha < 1)
1564 scoreboard_time = time;
1565 if(hud_configure_menu_open == 1)
1566 scoreboard_fade_alpha = 1;
1567 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1568 if (scoreboard_fadeinspeed && frametime)
1569 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1571 scoreboard_fade_alpha = 1;
1572 if(hud_fontsize_str != autocvar_hud_fontsize)
1574 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1575 Scoreboard_initFieldSizes();
1576 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1580 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1581 if (scoreboard_fadeoutspeed && frametime)
1582 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1584 scoreboard_fade_alpha = 0;
1587 if (!scoreboard_fade_alpha)
1589 scoreboard_acc_fade_alpha = 0;
1594 scoreboard_fade_alpha = 0;
1596 if (autocvar_hud_panel_scoreboard_dynamichud)
1599 HUD_Scale_Disable();
1601 if(scoreboard_fade_alpha <= 0)
1603 panel_fade_alpha *= scoreboard_fade_alpha;
1604 HUD_Panel_LoadCvars();
1606 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1607 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1608 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1609 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1610 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1611 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1612 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1614 // don't overlap with con_notify
1615 if(!autocvar__hud_configure)
1616 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1618 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1619 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1620 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1621 panel_size.x = fixed_scoreboard_width;
1623 Scoreboard_UpdatePlayerTeams();
1625 vector pos = panel_pos;
1630 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1632 // Begin of Game Info Section
1633 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1634 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1636 // Game Info: Game Type
1637 str = MapInfo_Type_ToText(gametype);
1638 draw_beginBoldFont();
1639 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);
1642 // Game Info: Game Detail
1643 float tl = STAT(TIMELIMIT);
1644 float fl = STAT(FRAGLIMIT);
1645 float ll = STAT(LEADLIMIT);
1646 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1649 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1650 if(!gametype.m_hidelimits)
1655 str = strcat(str, "^7 / "); // delimiter
1658 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1659 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1660 (teamscores_label(ts_primary) == "fastest") ? "" :
1661 TranslateScoresLabel(teamscores_label(ts_primary))));
1665 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1666 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1667 (scores_label(ps_primary) == "fastest") ? "" :
1668 TranslateScoresLabel(scores_label(ps_primary))));
1673 if(tl > 0 || fl > 0)
1676 if (ll_and_fl && fl > 0)
1677 str = strcat(str, "^7 & ");
1679 str = strcat(str, "^7 / ");
1684 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1685 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1686 (teamscores_label(ts_primary) == "fastest") ? "" :
1687 TranslateScoresLabel(teamscores_label(ts_primary))));
1691 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1692 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1693 (scores_label(ps_primary) == "fastest") ? "" :
1694 TranslateScoresLabel(scores_label(ps_primary))));
1699 pos.y += sb_gameinfo_type_fontsize.y;
1700 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
1702 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1703 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1704 // End of Game Info Section
1706 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1707 if(panel.current_panel_bg != "0")
1708 pos.y += panel_bg_border;
1710 // Draw the scoreboard
1711 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1714 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1718 vector panel_bg_color_save = panel_bg_color;
1719 vector team_score_baseoffset;
1720 vector team_size_baseoffset;
1721 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1723 // put team score to the left of scoreboard (and team size to the right)
1724 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1725 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1726 if(panel.current_panel_bg != "0")
1728 team_score_baseoffset.x -= panel_bg_border;
1729 team_size_baseoffset.x += panel_bg_border;
1734 // put team score to the right of scoreboard (and team size to the left)
1735 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1736 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1737 if(panel.current_panel_bg != "0")
1739 team_score_baseoffset.x += panel_bg_border;
1740 team_size_baseoffset.x -= panel_bg_border;
1744 int team_size_total = 0;
1745 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1747 // calculate team size total (sum of all team sizes)
1748 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1749 if(tm.team != NUM_SPECTATOR)
1750 team_size_total += tm.team_size;
1753 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1755 if(tm.team == NUM_SPECTATOR)
1760 draw_beginBoldFont();
1761 vector rgb = Team_ColorRGB(tm.team);
1762 str = ftos(tm.(teamscores(ts_primary)));
1763 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1765 // team score on the left (default)
1766 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1770 // team score on the right
1771 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1773 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1775 // team size (if set to show on the side)
1776 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1778 // calculate the starting position for the whole team size info string
1779 str = sprintf("%d/%d", tm.team_size, team_size_total);
1780 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1782 // team size on the left
1783 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1787 // team size on the right
1788 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1790 str = sprintf("%d", tm.team_size);
1791 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1792 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1793 str = sprintf("/%d", team_size_total);
1794 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1798 // secondary score, e.g. keyhunt
1799 if(ts_primary != ts_secondary)
1801 str = ftos(tm.(teamscores(ts_secondary)));
1802 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1805 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1810 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1813 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1816 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1817 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1818 else if(panel_bg_color_team > 0)
1819 panel_bg_color = rgb * panel_bg_color_team;
1821 panel_bg_color = rgb;
1822 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1824 panel_bg_color = panel_bg_color_save;
1828 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1829 if(tm.team != NUM_SPECTATOR)
1832 // display it anyway
1833 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1836 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1837 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1839 if(MUTATOR_CALLHOOK(ShowRankings)) {
1840 string ranktitle = M_ARGV(0, string);
1841 if(race_speedaward) {
1842 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);
1843 pos.y += 1.25 * hud_fontsize.y;
1845 if(race_speedaward_alltimebest) {
1846 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);
1847 pos.y += 1.25 * hud_fontsize.y;
1849 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1852 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1855 for(pl = players.sort_next; pl; pl = pl.sort_next)
1857 if(pl.team == NUM_SPECTATOR)
1859 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1860 if(tm.team == NUM_SPECTATOR)
1862 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1863 draw_beginBoldFont();
1864 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1866 pos.y += 1.25 * hud_fontsize.y;
1868 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1869 pos.y += 1.25 * hud_fontsize.y;
1876 // print information about respawn status
1877 float respawn_time = STAT(RESPAWN_TIME);
1881 if(respawn_time < 0)
1883 // a negative number means we are awaiting respawn, time value is still the same
1884 respawn_time *= -1; // remove mark now that we checked it
1886 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1887 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1889 str = sprintf(_("^1Respawning in ^3%s^1..."),
1890 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1891 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1893 count_seconds(ceil(respawn_time - time))
1897 else if(time < respawn_time)
1899 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1900 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1901 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1903 count_seconds(ceil(respawn_time - time))
1907 else if(time >= respawn_time)
1908 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1910 pos.y += 1.2 * hud_fontsize.y;
1911 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1914 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;