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_itemstats_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
90 bool autocvar_hud_panel_scoreboard_dynamichud = false;
92 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
93 bool autocvar_hud_panel_scoreboard_others_showscore = true;
94 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
95 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
96 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
98 // mode 0: returns translated label
99 // mode 1: prints name and description of all the labels
100 string Label_getInfo(string label, int mode)
103 label = "bckills"; // first case in the switch
107 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
108 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
109 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")));
110 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
111 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
112 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
113 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
114 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
115 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
116 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
117 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
118 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
119 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
120 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
121 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
122 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
123 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
124 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
125 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
126 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
127 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
128 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
129 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
130 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
131 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
132 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
133 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
134 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")));
135 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
136 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
137 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
138 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
139 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
140 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
141 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
142 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
143 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
144 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
145 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
146 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
147 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
148 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
149 default: return label;
154 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
155 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
157 void Scoreboard_InitScores()
161 ps_primary = ps_secondary = NULL;
162 ts_primary = ts_secondary = -1;
163 FOREACH(Scores, true, {
164 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
165 if(f == SFL_SORT_PRIO_PRIMARY)
167 if(f == SFL_SORT_PRIO_SECONDARY)
170 if(ps_secondary == NULL)
171 ps_secondary = ps_primary;
173 for(i = 0; i < MAX_TEAMSCORE; ++i)
175 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
181 if(ts_secondary == -1)
182 ts_secondary = ts_primary;
184 Cmd_Scoreboard_SetFields(0);
188 void Scoreboard_UpdatePlayerTeams()
192 for(pl = players.sort_next; pl; pl = pl.sort_next)
195 int Team = entcs_GetScoreTeam(pl.sv_entnum);
196 if(SetTeam(pl, Team))
199 Scoreboard_UpdatePlayerPos(pl);
203 pl = players.sort_next;
208 print(strcat("PNUM: ", ftos(num), "\n"));
213 int Scoreboard_CompareScore(int vl, int vr, int f)
215 TC(int, vl); TC(int, vr); TC(int, f);
216 if(f & SFL_ZERO_IS_WORST)
218 if(vl == 0 && vr != 0)
220 if(vl != 0 && vr == 0)
224 return IS_INCREASING(f);
226 return IS_DECREASING(f);
230 float Scoreboard_ComparePlayerScores(entity left, entity right)
233 vl = entcs_GetTeam(left.sv_entnum);
234 vr = entcs_GetTeam(right.sv_entnum);
246 if(vl == NUM_SPECTATOR)
248 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
250 if(!left.gotscores && right.gotscores)
255 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
259 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
263 FOREACH(Scores, true, {
264 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
265 if (r >= 0) return r;
268 if (left.sv_entnum < right.sv_entnum)
274 void Scoreboard_UpdatePlayerPos(entity player)
277 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
279 SORT_SWAP(player, ent);
281 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
283 SORT_SWAP(ent, player);
287 float Scoreboard_CompareTeamScores(entity left, entity right)
291 if(left.team == NUM_SPECTATOR)
293 if(right.team == NUM_SPECTATOR)
296 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
300 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
304 for(i = 0; i < MAX_TEAMSCORE; ++i)
306 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
311 if (left.team < right.team)
317 void Scoreboard_UpdateTeamPos(entity Team)
320 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
322 SORT_SWAP(Team, ent);
324 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
326 SORT_SWAP(ent, Team);
330 void Cmd_Scoreboard_Help()
332 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
333 LOG_HELP(_("Usage:"));
334 LOG_HELP("^2scoreboard_columns_set ^3default");
335 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
336 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
337 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
338 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
339 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
340 LOG_HELP(_("The following field names are recognized (case insensitive):"));
346 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
347 "of game types, then a slash, to make the field show up only in these\n"
348 "or in all but these game types. You can also specify 'all' as a\n"
349 "field to show all fields available for the current game mode."));
352 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
353 "include/exclude ALL teams/noteams game modes."));
356 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
357 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
358 "right of the vertical bar aligned to the right."));
359 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
360 "other gamemodes except DM."));
363 // NOTE: adding a gametype with ? to not warn for an optional field
364 // make sure it's excluded in a previous exclusive rule, if any
365 // otherwise the previous exclusive rule warns anyway
366 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
367 #define SCOREBOARD_DEFAULT_COLUMNS \
368 "ping pl fps name |" \
369 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
370 " -teams,lms/deaths +ft,tdm/deaths" \
372 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
373 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
374 " +tdm,ft,dom,ons,as/teamkills"\
375 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
376 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
377 " +lms/lives +lms/rank" \
378 " +kh/kckills +kh/losses +kh/caps" \
379 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
380 " +as/objectives +nb/faults +nb/goals" \
381 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
382 " +dom/ticks +dom/takes" \
383 " -lms,rc,cts,inv,nb/score"
385 void Cmd_Scoreboard_SetFields(int argc)
390 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
394 return; // do nothing, we don't know gametype and scores yet
396 // sbt_fields uses strunzone on the titles!
397 if(!sbt_field_title[0])
398 for(i = 0; i < MAX_SBT_FIELDS; ++i)
399 sbt_field_title[i] = strzone("(null)");
401 // TODO: re enable with gametype dependant cvars?
402 if(argc < 3) // no arguments provided
403 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
406 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
410 if(argv(2) == "default" || argv(2) == "expand_default")
412 if(argv(2) == "expand_default")
413 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
414 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
416 else if(argv(2) == "all")
418 string s = "ping pl name |"; // scores without a label
419 FOREACH(Scores, true, {
421 if(it != ps_secondary)
422 if(scores_label(it) != "")
423 s = strcat(s, " ", scores_label(it));
425 if(ps_secondary != ps_primary)
426 s = strcat(s, " ", scores_label(ps_secondary));
427 s = strcat(s, " ", scores_label(ps_primary));
428 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
435 hud_fontsize = HUD_GetFontsize("hud_fontsize");
437 for(i = 1; i < argc - 1; ++i)
440 bool nocomplain = false;
441 if(substring(str, 0, 1) == "?")
444 str = substring(str, 1, strlen(str) - 1);
447 slash = strstrofs(str, "/", 0);
450 pattern = substring(str, 0, slash);
451 str = substring(str, slash + 1, strlen(str) - (slash + 1));
453 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
457 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
458 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
459 str = strtolower(str);
464 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
465 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
466 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
467 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
468 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
469 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
470 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
471 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
472 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
473 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
476 FOREACH(Scores, true, {
477 if (str == strtolower(scores_label(it))) {
479 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
489 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
493 sbt_field[sbt_num_fields] = j;
496 if(j == ps_secondary)
497 have_secondary = true;
502 if(sbt_num_fields >= MAX_SBT_FIELDS)
506 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
508 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
509 have_secondary = true;
510 if(ps_primary == ps_secondary)
511 have_secondary = true;
512 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
514 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 0; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
526 sbt_field[0] = SP_NAME;
528 LOG_INFO("fixed missing field 'name'");
532 strunzone(sbt_field_title[sbt_num_fields]);
533 for(i = sbt_num_fields; i > 1; --i)
535 sbt_field_title[i] = sbt_field_title[i-1];
536 sbt_field_size[i] = sbt_field_size[i-1];
537 sbt_field[i] = sbt_field[i-1];
539 sbt_field_title[1] = strzone("|");
540 sbt_field[1] = SP_SEPARATOR;
541 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
543 LOG_INFO("fixed missing field '|'");
546 else if(!have_separator)
548 strcpy(sbt_field_title[sbt_num_fields], "|");
549 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
550 sbt_field[sbt_num_fields] = SP_SEPARATOR;
552 LOG_INFO("fixed missing field '|'");
556 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
557 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
558 sbt_field[sbt_num_fields] = ps_secondary;
560 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
564 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
565 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
566 sbt_field[sbt_num_fields] = ps_primary;
568 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
572 sbt_field[sbt_num_fields] = SP_END;
576 vector sbt_field_rgb;
577 string sbt_field_icon0;
578 string sbt_field_icon1;
579 string sbt_field_icon2;
580 vector sbt_field_icon0_rgb;
581 vector sbt_field_icon1_rgb;
582 vector sbt_field_icon2_rgb;
583 string Scoreboard_GetName(entity pl)
585 if(ready_waiting && pl.ready)
587 sbt_field_icon0 = "gfx/scoreboard/player_ready";
591 int f = entcs_GetClientColors(pl.sv_entnum);
593 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
594 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
595 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
596 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
597 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
600 return entcs_GetName(pl.sv_entnum);
603 string Scoreboard_GetField(entity pl, PlayerScoreField field)
605 float tmp, num, denom;
608 sbt_field_rgb = '1 1 1';
609 sbt_field_icon0 = "";
610 sbt_field_icon1 = "";
611 sbt_field_icon2 = "";
612 sbt_field_icon0_rgb = '1 1 1';
613 sbt_field_icon1_rgb = '1 1 1';
614 sbt_field_icon2_rgb = '1 1 1';
619 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
620 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
624 tmp = max(0, min(220, f-80)) / 220;
625 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
631 f = pl.ping_packetloss;
632 tmp = pl.ping_movementloss;
633 if(f == 0 && tmp == 0)
635 str = ftos(ceil(f * 100));
637 str = strcat(str, "~", ftos(ceil(tmp * 100)));
638 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
639 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
643 return Scoreboard_GetName(pl);
646 f = pl.(scores(SP_KILLS));
647 f -= pl.(scores(SP_SUICIDES));
651 num = pl.(scores(SP_KILLS));
652 denom = pl.(scores(SP_DEATHS));
655 sbt_field_rgb = '0 1 0';
656 str = sprintf("%d", num);
657 } else if(num <= 0) {
658 sbt_field_rgb = '1 0 0';
659 str = sprintf("%.1f", num/denom);
661 str = sprintf("%.1f", num/denom);
665 f = pl.(scores(SP_KILLS));
666 f -= pl.(scores(SP_DEATHS));
669 sbt_field_rgb = '0 1 0';
671 sbt_field_rgb = '1 1 1';
673 sbt_field_rgb = '1 0 0';
679 float elo = pl.(scores(SP_ELO));
681 case -1: return "...";
682 case -2: return _("N/A");
683 default: return ftos(elo);
689 float fps = pl.(scores(SP_FPS));
692 sbt_field_rgb = '1 1 1';
693 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
695 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
696 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
700 case SP_DMG: case SP_DMGTAKEN:
701 return sprintf("%.1f k", pl.(scores(field)) / 1000);
703 default: case SP_SCORE:
704 tmp = pl.(scores(field));
705 f = scores_flags(field);
706 if(field == ps_primary)
707 sbt_field_rgb = '1 1 0';
708 else if(field == ps_secondary)
709 sbt_field_rgb = '0 1 1';
711 sbt_field_rgb = '1 1 1';
712 return ScoreString(f, tmp);
717 float sbt_fixcolumnwidth_len;
718 float sbt_fixcolumnwidth_iconlen;
719 float sbt_fixcolumnwidth_marginlen;
721 string Scoreboard_FixColumnWidth(int i, string str)
727 sbt_fixcolumnwidth_iconlen = 0;
729 if(sbt_field_icon0 != "")
731 sz = draw_getimagesize(sbt_field_icon0);
733 if(sbt_fixcolumnwidth_iconlen < f)
734 sbt_fixcolumnwidth_iconlen = f;
737 if(sbt_field_icon1 != "")
739 sz = draw_getimagesize(sbt_field_icon1);
741 if(sbt_fixcolumnwidth_iconlen < f)
742 sbt_fixcolumnwidth_iconlen = f;
745 if(sbt_field_icon2 != "")
747 sz = draw_getimagesize(sbt_field_icon2);
749 if(sbt_fixcolumnwidth_iconlen < f)
750 sbt_fixcolumnwidth_iconlen = f;
753 if(sbt_fixcolumnwidth_iconlen != 0)
755 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
756 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
759 sbt_fixcolumnwidth_marginlen = 0;
761 if(sbt_field[i] == SP_NAME) // name gets all remaining space
764 float remaining_space = 0;
765 for(j = 0; j < sbt_num_fields; ++j)
767 if (sbt_field[i] != SP_SEPARATOR)
768 remaining_space += sbt_field_size[j] + hud_fontsize.x;
769 sbt_field_size[i] = panel_size.x - remaining_space;
771 if (sbt_fixcolumnwidth_iconlen != 0)
772 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
773 float namesize = panel_size.x - remaining_space;
774 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
775 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
777 max_namesize = vid_conwidth - remaining_space;
780 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
782 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
783 if(sbt_field_size[i] < f)
784 sbt_field_size[i] = f;
789 void Scoreboard_initFieldSizes()
791 for(int i = 0; i < sbt_num_fields; ++i)
793 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
794 Scoreboard_FixColumnWidth(i, "");
798 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
801 vector column_dim = eY * panel_size.y;
803 column_dim.y -= 1.25 * hud_fontsize.y;
804 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
805 pos.x += hud_fontsize.x * 0.5;
806 for(i = 0; i < sbt_num_fields; ++i)
808 if(sbt_field[i] == SP_SEPARATOR)
810 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
813 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
814 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
815 pos.x += column_dim.x;
817 if(sbt_field[i] == SP_SEPARATOR)
819 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
820 for(i = sbt_num_fields - 1; i > 0; --i)
822 if(sbt_field[i] == SP_SEPARATOR)
825 pos.x -= sbt_field_size[i];
830 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
831 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
834 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
835 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
836 pos.x -= hud_fontsize.x;
841 pos.y += 1.25 * hud_fontsize.y;
845 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
847 TC(bool, is_self); TC(int, pl_number);
849 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
851 vector h_pos = item_pos;
852 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
853 // alternated rows highlighting
855 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
856 else if((sbt_highlight) && (!(pl_number % 2)))
857 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
859 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
861 vector pos = item_pos;
862 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
864 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);
866 pos.x += hud_fontsize.x * 0.5;
867 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
868 vector tmp = '0 0 0';
870 PlayerScoreField field;
871 for(i = 0; i < sbt_num_fields; ++i)
873 field = sbt_field[i];
874 if(field == SP_SEPARATOR)
877 if(is_spec && field != SP_NAME && field != SP_PING) {
878 pos.x += sbt_field_size[i] + hud_fontsize.x;
881 str = Scoreboard_GetField(pl, field);
882 str = Scoreboard_FixColumnWidth(i, str);
884 pos.x += sbt_field_size[i] + hud_fontsize.x;
886 if(field == SP_NAME) {
887 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
888 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
890 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
891 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
894 tmp.x = sbt_field_size[i] + hud_fontsize.x;
895 if(sbt_field_icon0 != "")
896 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
897 if(sbt_field_icon1 != "")
898 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
899 if(sbt_field_icon2 != "")
900 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
903 if(sbt_field[i] == SP_SEPARATOR)
905 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
906 for(i = sbt_num_fields-1; i > 0; --i)
908 field = sbt_field[i];
909 if(field == SP_SEPARATOR)
912 if(is_spec && field != SP_NAME && field != SP_PING) {
913 pos.x -= sbt_field_size[i] + hud_fontsize.x;
917 str = Scoreboard_GetField(pl, field);
918 str = Scoreboard_FixColumnWidth(i, str);
920 if(field == SP_NAME) {
921 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
922 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
924 tmp.x = sbt_fixcolumnwidth_len;
925 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 tmp.x = sbt_field_size[i];
929 if(sbt_field_icon0 != "")
930 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
931 if(sbt_field_icon1 != "")
932 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
933 if(sbt_field_icon2 != "")
934 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
935 pos.x -= sbt_field_size[i] + hud_fontsize.x;
940 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
943 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
946 vector h_pos = item_pos;
947 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
949 bool complete = (this_team == NUM_SPECTATOR);
952 if((sbt_highlight) && (!(pl_number % 2)))
953 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
955 vector pos = item_pos;
956 pos.x += hud_fontsize.x * 0.5;
957 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
959 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
961 width_limit -= stringwidth("...", false, hud_fontsize);
962 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
963 static float max_name_width = 0;
966 float min_fieldsize = 0;
967 float fieldpadding = hud_fontsize.x * 0.25;
968 if(this_team == NUM_SPECTATOR)
970 if(autocvar_hud_panel_scoreboard_spectators_showping)
971 min_fieldsize = stringwidth("999", false, hud_fontsize);
973 else if(autocvar_hud_panel_scoreboard_others_showscore)
974 min_fieldsize = stringwidth("99", false, hud_fontsize);
975 for(i = 0; pl; pl = pl.sort_next)
977 if(pl.team != this_team)
983 if(this_team == NUM_SPECTATOR)
985 if(autocvar_hud_panel_scoreboard_spectators_showping)
986 field = Scoreboard_GetField(pl, SP_PING);
988 else if(autocvar_hud_panel_scoreboard_others_showscore)
989 field = Scoreboard_GetField(pl, SP_SCORE);
991 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
992 float column_width = stringwidth(str, true, hud_fontsize);
993 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
995 if(column_width > max_name_width)
996 max_name_width = column_width;
997 column_width = max_name_width;
1001 fieldsize = stringwidth(field, false, hud_fontsize);
1002 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1005 if(pos.x + column_width > width_limit)
1010 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1015 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1016 pos.y += hud_fontsize.y * 1.25;
1020 vector name_pos = pos;
1021 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1022 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1023 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1026 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1027 h_size.y = hud_fontsize.y;
1028 vector field_pos = pos;
1029 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1030 field_pos.x += column_width - h_size.x;
1032 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1033 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1034 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1038 h_size.x = column_width + hud_fontsize.x * 0.25;
1039 h_size.y = hud_fontsize.y;
1040 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1042 pos.x += column_width;
1043 pos.x += hud_fontsize.x;
1045 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1048 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1050 int max_players = 999;
1051 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1053 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1056 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1057 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1058 height /= team_count;
1061 height -= panel_bg_padding * 2; // - padding
1062 max_players = floor(height / (hud_fontsize.y * 1.25));
1063 if(max_players <= 1)
1065 if(max_players == tm.team_size)
1070 entity me = playerslots[current_player];
1072 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1073 panel_size.y += panel_bg_padding * 2;
1076 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1077 if(panel.current_panel_bg != "0")
1078 end_pos.y += panel_bg_border * 2;
1080 if(panel_bg_padding)
1082 panel_pos += '1 1 0' * panel_bg_padding;
1083 panel_size -= '2 2 0' * panel_bg_padding;
1087 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1091 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1093 pos.y += 1.25 * hud_fontsize.y;
1096 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1098 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1101 // print header row and highlight columns
1102 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1104 // fill the table and draw the rows
1105 bool is_self = false;
1106 bool self_shown = false;
1108 for(pl = players.sort_next; pl; pl = pl.sort_next)
1110 if(pl.team != tm.team)
1112 if(i == max_players - 2 && pl != me)
1114 if(!self_shown && me.team == tm.team)
1116 Scoreboard_DrawItem(pos, rgb, me, true, i);
1118 pos.y += 1.25 * hud_fontsize.y;
1122 if(i >= max_players - 1)
1124 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1127 is_self = (pl.sv_entnum == current_player);
1128 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1131 pos.y += 1.25 * hud_fontsize.y;
1135 panel_size.x += panel_bg_padding * 2; // restore initial width
1139 bool Scoreboard_WouldDraw()
1141 if (MUTATOR_CALLHOOK(DrawScoreboard))
1143 else if (QuickMenu_IsOpened())
1145 else if (HUD_Radar_Clickable())
1147 else if (scoreboard_showscores)
1149 else if (intermission == 1)
1151 else if (intermission == 2)
1153 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1154 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1158 else if (scoreboard_showscores_force)
1163 float average_accuracy;
1164 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1168 if (scoreboard_fade_alpha == 1)
1169 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1171 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1173 vector initial_pos = pos;
1175 WepSet weapons_stat = WepSet_GetFromStat();
1176 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1177 int disownedcnt = 0;
1179 FOREACH(Weapons, it != WEP_Null, {
1180 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1182 WepSet set = it.m_wepset;
1183 if(it.spawnflags & WEP_TYPE_OTHER)
1188 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1190 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1197 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1198 if (weapon_cnt <= 0) return pos;
1201 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1203 int columnns = ceil(weapon_cnt / rows);
1205 float weapon_height = 29;
1206 float height = hud_fontsize.y + weapon_height;
1208 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);
1209 pos.y += 1.25 * hud_fontsize.y;
1210 if(panel.current_panel_bg != "0")
1211 pos.y += panel_bg_border;
1214 panel_size.y = height * rows;
1215 panel_size.y += panel_bg_padding * 2;
1217 float panel_bg_alpha_save = panel_bg_alpha;
1218 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1220 panel_bg_alpha = panel_bg_alpha_save;
1222 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1223 if(panel.current_panel_bg != "0")
1224 end_pos.y += panel_bg_border * 2;
1226 if(panel_bg_padding)
1228 panel_pos += '1 1 0' * panel_bg_padding;
1229 panel_size -= '2 2 0' * panel_bg_padding;
1233 vector tmp = panel_size;
1235 float weapon_width = tmp.x / columnns / rows;
1238 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1242 // column highlighting
1243 for (int i = 0; i < columnns; ++i)
1245 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);
1248 for (int i = 0; i < rows; ++i)
1249 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1252 average_accuracy = 0;
1253 int weapons_with_stats = 0;
1255 pos.x += weapon_width / 2;
1257 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1260 Accuracy_LoadColors();
1262 float oldposx = pos.x;
1266 FOREACH(Weapons, it != WEP_Null, {
1267 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1269 WepSet set = it.m_wepset;
1270 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1272 if (it.spawnflags & WEP_TYPE_OTHER)
1276 if (weapon_stats >= 0)
1277 weapon_alpha = sbt_fg_alpha;
1279 weapon_alpha = 0.2 * sbt_fg_alpha;
1282 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1284 if (weapon_stats >= 0) {
1285 weapons_with_stats += 1;
1286 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1289 s = sprintf("%d%%", weapon_stats * 100);
1292 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1294 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1295 rgb = Accuracy_GetColor(weapon_stats);
1297 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1299 tmpos.x += weapon_width * rows;
1300 pos.x += weapon_width * rows;
1301 if (rows == 2 && column == columnns - 1) {
1309 if (weapons_with_stats)
1310 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1312 panel_size.x += panel_bg_padding * 2; // restore initial width
1314 if (scoreboard_acc_fade_alpha == 1)
1316 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1319 .bool uninteresting;
1320 STATIC_INIT(default_order_items_label)
1322 IL_EACH(default_order_items, true, {
1330 case "vaporizer_cells":
1333 case "armor_medium":
1334 case "health_small":
1335 case "health_medium":
1336 it.uninteresting = true;
1341 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1343 float scoreboard_acc_fade_alpha_save = scoreboard_acc_fade_alpha; // debug
1344 scoreboard_acc_fade_alpha = 1; // debug: make Item Stats always visible
1346 float initial_posx = pos.x;
1347 int disowned_cnt = 0;
1348 int uninteresting_cnt = 0;
1349 IL_EACH(default_order_items, true, {
1350 int q = g_inventory.inv_items[it.m_id];
1351 //q = 1; // debug: display all items
1352 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1353 ++uninteresting_cnt;
1357 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1358 int n = items_cnt - disowned_cnt;
1359 if (n <= 0) return pos;
1361 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1362 int columnns = max(6, ceil(n / rows));
1365 float fontsize = height * 1/3;
1366 float item_height = height * 2/3;
1368 drawstring(pos, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1369 pos.y += 1.25 * hud_fontsize.y;
1370 if(panel.current_panel_bg != "0")
1371 pos.y += panel_bg_border;
1374 panel_size.y = height * rows;
1375 panel_size.y += panel_bg_padding * 2;
1377 float panel_bg_alpha_save = panel_bg_alpha;
1378 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1380 panel_bg_alpha = panel_bg_alpha_save;
1382 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1383 if(panel.current_panel_bg != "0")
1384 end_pos.y += panel_bg_border * 2;
1386 if(panel_bg_padding)
1388 panel_pos += '1 1 0' * panel_bg_padding;
1389 panel_size -= '2 2 0' * panel_bg_padding;
1393 vector tmp = panel_size;
1395 float item_width = tmp.x / columnns / rows;
1398 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1402 // column highlighting
1403 for (int i = 0; i < columnns; ++i)
1405 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);
1408 for (int i = 0; i < rows; ++i)
1409 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);
1413 pos.x += item_width / 2;
1415 float oldposx = pos.x;
1419 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1420 int n = g_inventory.inv_items[it.m_id];
1421 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1422 if (n <= 0) continue;
1423 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);
1425 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1426 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1427 tmpos.x += item_width * rows;
1428 pos.x += item_width * rows;
1429 if (rows == 2 && column == columnns - 1) {
1437 pos.y += 1.25 * hud_fontsize.y;
1438 pos.x = initial_posx;
1440 panel_size.x += panel_bg_padding * 2; // restore initial width
1442 scoreboard_acc_fade_alpha = scoreboard_acc_fade_alpha_save; // debug
1446 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1448 pos.x += hud_fontsize.x * 0.25;
1449 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1450 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1451 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1453 pos.y += hud_fontsize.y;
1458 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1459 float stat_secrets_found, stat_secrets_total;
1460 float stat_monsters_killed, stat_monsters_total;
1464 // get monster stats
1465 stat_monsters_killed = STAT(MONSTERS_KILLED);
1466 stat_monsters_total = STAT(MONSTERS_TOTAL);
1468 // get secrets stats
1469 stat_secrets_found = STAT(SECRETS_FOUND);
1470 stat_secrets_total = STAT(SECRETS_TOTAL);
1472 // get number of rows
1473 if(stat_secrets_total)
1475 if(stat_monsters_total)
1478 // if no rows, return
1482 // draw table header
1483 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1484 pos.y += 1.25 * hud_fontsize.y;
1485 if(panel.current_panel_bg != "0")
1486 pos.y += panel_bg_border;
1489 panel_size.y = hud_fontsize.y * rows;
1490 panel_size.y += panel_bg_padding * 2;
1493 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1494 if(panel.current_panel_bg != "0")
1495 end_pos.y += panel_bg_border * 2;
1497 if(panel_bg_padding)
1499 panel_pos += '1 1 0' * panel_bg_padding;
1500 panel_size -= '2 2 0' * panel_bg_padding;
1504 vector tmp = panel_size;
1507 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1510 if(stat_monsters_total)
1512 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1513 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1517 if(stat_secrets_total)
1519 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1520 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1523 panel_size.x += panel_bg_padding * 2; // restore initial width
1528 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1531 RANKINGS_RECEIVED_CNT = 0;
1532 for (i=RANKINGS_CNT-1; i>=0; --i)
1534 ++RANKINGS_RECEIVED_CNT;
1536 if (RANKINGS_RECEIVED_CNT == 0)
1539 vector hl_rgb = rgb + '0.5 0.5 0.5';
1541 pos.y += hud_fontsize.y;
1542 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1543 pos.y += 1.25 * hud_fontsize.y;
1544 if(panel.current_panel_bg != "0")
1545 pos.y += panel_bg_border;
1550 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1552 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1557 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1559 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1563 float ranksize = 3 * hud_fontsize.x;
1564 float timesize = 5 * hud_fontsize.x;
1565 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1566 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1567 columns = min(columns, RANKINGS_RECEIVED_CNT);
1569 // expand name column to fill the entire row
1570 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1571 namesize += available_space;
1572 columnsize.x += available_space;
1574 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1575 panel_size.y += panel_bg_padding * 2;
1579 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1580 if(panel.current_panel_bg != "0")
1581 end_pos.y += panel_bg_border * 2;
1583 if(panel_bg_padding)
1585 panel_pos += '1 1 0' * panel_bg_padding;
1586 panel_size -= '2 2 0' * panel_bg_padding;
1592 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1594 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1596 int column = 0, j = 0;
1597 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1598 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1605 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1606 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1607 else if(!((j + column) & 1) && sbt_highlight)
1608 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1610 str = count_ordinal(i+1);
1611 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1612 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1613 str = ColorTranslateRGB(grecordholder[i]);
1615 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1616 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1618 pos.y += 1.25 * hud_fontsize.y;
1620 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1624 pos.x += panel_size.x / columns;
1625 pos.y = panel_pos.y;
1628 strfree(zoned_name_self);
1630 panel_size.x += panel_bg_padding * 2; // restore initial width
1634 float scoreboard_time;
1635 bool have_weapon_stats;
1636 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1638 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1640 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1643 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1644 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1650 if (!have_weapon_stats)
1652 FOREACH(Weapons, it != WEP_Null, {
1653 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1654 if (weapon_stats >= 0)
1656 have_weapon_stats = true;
1660 if (!have_weapon_stats)
1667 void Scoreboard_Draw()
1669 if(!autocvar__hud_configure)
1671 if(!hud_draw_maximized) return;
1673 // frametime checks allow to toggle the scoreboard even when the game is paused
1674 if(scoreboard_active) {
1675 if (scoreboard_fade_alpha < 1)
1676 scoreboard_time = time;
1677 if(hud_configure_menu_open == 1)
1678 scoreboard_fade_alpha = 1;
1679 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1680 if (scoreboard_fadeinspeed && frametime)
1681 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1683 scoreboard_fade_alpha = 1;
1684 if(hud_fontsize_str != autocvar_hud_fontsize)
1686 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1687 Scoreboard_initFieldSizes();
1688 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1692 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1693 if (scoreboard_fadeoutspeed && frametime)
1694 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1696 scoreboard_fade_alpha = 0;
1699 if (!scoreboard_fade_alpha)
1701 scoreboard_acc_fade_alpha = 0;
1706 scoreboard_fade_alpha = 0;
1708 if (autocvar_hud_panel_scoreboard_dynamichud)
1711 HUD_Scale_Disable();
1713 if(scoreboard_fade_alpha <= 0)
1715 panel_fade_alpha *= scoreboard_fade_alpha;
1716 HUD_Panel_LoadCvars();
1718 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1719 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1720 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1721 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1722 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1723 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1725 // don't overlap with con_notify
1726 if(!autocvar__hud_configure)
1727 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1729 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1730 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1731 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1732 panel_size.x = fixed_scoreboard_width;
1734 Scoreboard_UpdatePlayerTeams();
1736 vector pos = panel_pos;
1741 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1743 // Begin of Game Info Section
1744 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1745 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1747 // Game Info: Game Type
1748 str = MapInfo_Type_ToText(gametype);
1749 draw_beginBoldFont();
1750 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);
1753 // Game Info: Game Detail
1754 float tl = STAT(TIMELIMIT);
1755 float fl = STAT(FRAGLIMIT);
1756 float ll = STAT(LEADLIMIT);
1757 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1760 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1761 if(!gametype.m_hidelimits)
1766 str = strcat(str, "^7 / "); // delimiter
1769 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1770 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1771 (teamscores_label(ts_primary) == "fastest") ? "" :
1772 TranslateScoresLabel(teamscores_label(ts_primary))));
1776 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1777 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1778 (scores_label(ps_primary) == "fastest") ? "" :
1779 TranslateScoresLabel(scores_label(ps_primary))));
1784 if(tl > 0 || fl > 0)
1787 if (ll_and_fl && fl > 0)
1788 str = strcat(str, "^7 & ");
1790 str = strcat(str, "^7 / ");
1795 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1796 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1797 (teamscores_label(ts_primary) == "fastest") ? "" :
1798 TranslateScoresLabel(teamscores_label(ts_primary))));
1802 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1803 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1804 (scores_label(ps_primary) == "fastest") ? "" :
1805 TranslateScoresLabel(scores_label(ps_primary))));
1810 pos.y += sb_gameinfo_type_fontsize.y;
1811 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
1813 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1814 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1815 // End of Game Info Section
1817 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1818 if(panel.current_panel_bg != "0")
1819 pos.y += panel_bg_border;
1821 // Draw the scoreboard
1822 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1825 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1829 vector panel_bg_color_save = panel_bg_color;
1830 vector team_score_baseoffset;
1831 vector team_size_baseoffset;
1832 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1834 // put team score to the left of scoreboard (and team size to the right)
1835 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1836 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1837 if(panel.current_panel_bg != "0")
1839 team_score_baseoffset.x -= panel_bg_border;
1840 team_size_baseoffset.x += panel_bg_border;
1845 // put team score to the right of scoreboard (and team size to the left)
1846 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1847 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1848 if(panel.current_panel_bg != "0")
1850 team_score_baseoffset.x += panel_bg_border;
1851 team_size_baseoffset.x -= panel_bg_border;
1855 int team_size_total = 0;
1856 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1858 // calculate team size total (sum of all team sizes)
1859 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1860 if(tm.team != NUM_SPECTATOR)
1861 team_size_total += tm.team_size;
1864 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1866 if(tm.team == NUM_SPECTATOR)
1871 draw_beginBoldFont();
1872 vector rgb = Team_ColorRGB(tm.team);
1873 str = ftos(tm.(teamscores(ts_primary)));
1874 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1876 // team score on the left (default)
1877 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1881 // team score on the right
1882 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1884 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1886 // team size (if set to show on the side)
1887 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1889 // calculate the starting position for the whole team size info string
1890 str = sprintf("%d/%d", tm.team_size, team_size_total);
1891 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1893 // team size on the left
1894 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1898 // team size on the right
1899 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1901 str = sprintf("%d", tm.team_size);
1902 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1903 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1904 str = sprintf("/%d", team_size_total);
1905 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1909 // secondary score, e.g. keyhunt
1910 if(ts_primary != ts_secondary)
1912 str = ftos(tm.(teamscores(ts_secondary)));
1913 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1916 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1921 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1924 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1927 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1928 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1929 else if(panel_bg_color_team > 0)
1930 panel_bg_color = rgb * panel_bg_color_team;
1932 panel_bg_color = rgb;
1933 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1935 panel_bg_color = panel_bg_color_save;
1939 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1940 if(tm.team != NUM_SPECTATOR)
1943 // display it anyway
1944 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1947 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1948 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1949 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1951 if(MUTATOR_CALLHOOK(ShowRankings)) {
1952 string ranktitle = M_ARGV(0, string);
1953 if(race_speedaward) {
1954 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);
1955 pos.y += 1.25 * hud_fontsize.y;
1957 if(race_speedaward_alltimebest) {
1958 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);
1959 pos.y += 1.25 * hud_fontsize.y;
1961 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1964 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1967 for(pl = players.sort_next; pl; pl = pl.sort_next)
1969 if(pl.team == NUM_SPECTATOR)
1971 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1972 if(tm.team == NUM_SPECTATOR)
1974 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1975 draw_beginBoldFont();
1976 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1978 pos.y += 1.25 * hud_fontsize.y;
1980 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1981 pos.y += 1.25 * hud_fontsize.y;
1988 // print information about respawn status
1989 float respawn_time = STAT(RESPAWN_TIME);
1993 if(respawn_time < 0)
1995 // a negative number means we are awaiting respawn, time value is still the same
1996 respawn_time *= -1; // remove mark now that we checked it
1998 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1999 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2001 str = sprintf(_("^1Respawning in ^3%s^1..."),
2002 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2003 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2005 count_seconds(ceil(respawn_time - time))
2009 else if(time < respawn_time)
2011 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2012 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2013 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2015 count_seconds(ceil(respawn_time - time))
2019 else if(time >= respawn_time)
2020 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2022 pos.y += 1.2 * hud_fontsize.y;
2023 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2026 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;