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