1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
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_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
93 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_dynamichud = false;
98 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
99 bool autocvar_hud_panel_scoreboard_others_showscore = true;
100 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
101 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
102 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 bool autocvar_hud_panel_scoreboard_playerid = false;
104 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
105 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
107 // mode 0: returns translated label
108 // mode 1: prints name and description of all the labels
109 string Label_getInfo(string label, int mode)
112 label = "bckills"; // first case in the switch
116 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
117 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
118 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")));
119 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
120 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
121 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
122 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
123 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
124 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
125 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
126 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
127 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
128 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
129 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
130 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
131 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
132 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
133 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
134 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
135 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
136 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
137 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
138 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
139 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
140 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
141 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
142 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
143 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")));
144 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
145 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
146 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
147 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
148 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
149 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
150 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
151 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
152 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
153 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
154 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
155 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
156 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
157 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
158 default: return label;
163 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
164 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
166 void Scoreboard_InitScores()
170 ps_primary = ps_secondary = NULL;
171 ts_primary = ts_secondary = -1;
172 FOREACH(Scores, true, {
173 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
174 if(f == SFL_SORT_PRIO_PRIMARY)
176 if(f == SFL_SORT_PRIO_SECONDARY)
179 if(ps_secondary == NULL)
180 ps_secondary = ps_primary;
182 for(i = 0; i < MAX_TEAMSCORE; ++i)
184 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
185 if(f == SFL_SORT_PRIO_PRIMARY)
187 if(f == SFL_SORT_PRIO_SECONDARY)
190 if(ts_secondary == -1)
191 ts_secondary = ts_primary;
193 Cmd_Scoreboard_SetFields(0);
197 void Scoreboard_UpdatePlayerTeams()
201 for(pl = players.sort_next; pl; pl = pl.sort_next)
204 int Team = entcs_GetScoreTeam(pl.sv_entnum);
205 if(SetTeam(pl, Team))
208 Scoreboard_UpdatePlayerPos(pl);
212 pl = players.sort_next;
217 print(strcat("PNUM: ", ftos(num), "\n"));
222 int Scoreboard_CompareScore(int vl, int vr, int f)
224 TC(int, vl); TC(int, vr); TC(int, f);
225 if(f & SFL_ZERO_IS_WORST)
227 if(vl == 0 && vr != 0)
229 if(vl != 0 && vr == 0)
233 return IS_INCREASING(f);
235 return IS_DECREASING(f);
239 float Scoreboard_ComparePlayerScores(entity left, entity right)
242 vl = entcs_GetTeam(left.sv_entnum);
243 vr = entcs_GetTeam(right.sv_entnum);
255 if(vl == NUM_SPECTATOR)
257 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
259 if(!left.gotscores && right.gotscores)
264 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
268 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
272 FOREACH(Scores, true, {
273 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
274 if (r >= 0) return r;
277 if (left.sv_entnum < right.sv_entnum)
283 void Scoreboard_UpdatePlayerPos(entity player)
286 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
288 SORT_SWAP(player, ent);
290 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
292 SORT_SWAP(ent, player);
296 float Scoreboard_CompareTeamScores(entity left, entity right)
300 if(left.team == NUM_SPECTATOR)
302 if(right.team == NUM_SPECTATOR)
305 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
309 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
313 for(i = 0; i < MAX_TEAMSCORE; ++i)
315 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
320 if (left.team < right.team)
326 void Scoreboard_UpdateTeamPos(entity Team)
329 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
331 SORT_SWAP(Team, ent);
333 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
335 SORT_SWAP(ent, Team);
339 void Cmd_Scoreboard_Help()
341 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
342 LOG_HELP(_("Usage:"));
343 LOG_HELP("^2scoreboard_columns_set ^3default");
344 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
345 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
346 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
347 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
348 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
349 LOG_HELP(_("The following field names are recognized (case insensitive):"));
355 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
356 "of game types, then a slash, to make the field show up only in these\n"
357 "or in all but these game types. You can also specify 'all' as a\n"
358 "field to show all fields available for the current game mode."));
361 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
362 "include/exclude ALL teams/noteams game modes."));
365 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
366 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
367 "right of the vertical bar aligned to the right."));
368 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
369 "other gamemodes except DM."));
372 // NOTE: adding a gametype with ? to not warn for an optional field
373 // make sure it's excluded in a previous exclusive rule, if any
374 // otherwise the previous exclusive rule warns anyway
375 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
376 #define SCOREBOARD_DEFAULT_COLUMNS \
377 "ping pl fps name |" \
378 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
379 " -teams,lms/deaths +ft,tdm/deaths" \
381 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
382 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
383 " +tdm,ft,dom,ons,as/teamkills"\
384 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
385 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
386 " +lms/lives +lms/rank" \
387 " +kh/kckills +kh/losses +kh/caps" \
388 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
389 " +as/objectives +nb/faults +nb/goals" \
390 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
391 " +dom/ticks +dom/takes" \
392 " -lms,rc,cts,inv,nb/score"
394 void Cmd_Scoreboard_SetFields(int argc)
399 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
403 return; // do nothing, we don't know gametype and scores yet
405 // sbt_fields uses strunzone on the titles!
406 if(!sbt_field_title[0])
407 for(i = 0; i < MAX_SBT_FIELDS; ++i)
408 sbt_field_title[i] = strzone("(null)");
410 // TODO: re enable with gametype dependant cvars?
411 if(argc < 3) // no arguments provided
412 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
415 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
419 if(argv(2) == "default" || argv(2) == "expand_default")
421 if(argv(2) == "expand_default")
422 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
423 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
425 else if(argv(2) == "all")
427 string s = "ping pl name |"; // scores without a label
428 FOREACH(Scores, true, {
430 if(it != ps_secondary)
431 if(scores_label(it) != "")
432 s = strcat(s, " ", scores_label(it));
434 if(ps_secondary != ps_primary)
435 s = strcat(s, " ", scores_label(ps_secondary));
436 s = strcat(s, " ", scores_label(ps_primary));
437 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
444 hud_fontsize = HUD_GetFontsize("hud_fontsize");
446 for(i = 1; i < argc - 1; ++i)
449 bool nocomplain = false;
450 if(substring(str, 0, 1) == "?")
453 str = substring(str, 1, strlen(str) - 1);
456 slash = strstrofs(str, "/", 0);
459 pattern = substring(str, 0, slash);
460 str = substring(str, slash + 1, strlen(str) - (slash + 1));
462 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
466 str = strtolower(str);
467 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
468 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
473 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
474 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
475 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
476 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
477 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
478 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
479 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
480 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
481 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
484 FOREACH(Scores, true, {
485 if (str == strtolower(scores_label(it))) {
487 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
496 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
497 if(!nocomplain && str != "fps") // server can disable the fps field
498 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
500 strfree(sbt_field_title[sbt_num_fields]);
501 sbt_field_size[sbt_num_fields] = 0;
505 sbt_field[sbt_num_fields] = j;
508 if(j == ps_secondary)
509 have_secondary = true;
514 if(sbt_num_fields >= MAX_SBT_FIELDS)
518 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
520 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
521 have_secondary = true;
522 if(ps_primary == ps_secondary)
523 have_secondary = true;
524 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
526 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
530 strunzone(sbt_field_title[sbt_num_fields]);
531 for(i = sbt_num_fields; i > 0; --i)
533 sbt_field_title[i] = sbt_field_title[i-1];
534 sbt_field_size[i] = sbt_field_size[i-1];
535 sbt_field[i] = sbt_field[i-1];
537 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
538 sbt_field[0] = SP_NAME;
540 LOG_INFO("fixed missing field 'name'");
544 strunzone(sbt_field_title[sbt_num_fields]);
545 for(i = sbt_num_fields; i > 1; --i)
547 sbt_field_title[i] = sbt_field_title[i-1];
548 sbt_field_size[i] = sbt_field_size[i-1];
549 sbt_field[i] = sbt_field[i-1];
551 sbt_field_title[1] = strzone("|");
552 sbt_field[1] = SP_SEPARATOR;
553 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
555 LOG_INFO("fixed missing field '|'");
558 else if(!have_separator)
560 strcpy(sbt_field_title[sbt_num_fields], "|");
561 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
562 sbt_field[sbt_num_fields] = SP_SEPARATOR;
564 LOG_INFO("fixed missing field '|'");
568 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
569 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
570 sbt_field[sbt_num_fields] = ps_secondary;
572 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
576 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
577 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
578 sbt_field[sbt_num_fields] = ps_primary;
580 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
584 sbt_field[sbt_num_fields] = SP_END;
587 string Scoreboard_AddPlayerId(string pl_name, entity pl)
589 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
590 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
591 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
595 vector sbt_field_rgb;
596 string sbt_field_icon0;
597 string sbt_field_icon1;
598 string sbt_field_icon2;
599 vector sbt_field_icon0_rgb;
600 vector sbt_field_icon1_rgb;
601 vector sbt_field_icon2_rgb;
602 string Scoreboard_GetName(entity pl)
604 if(ready_waiting && pl.ready)
606 sbt_field_icon0 = "gfx/scoreboard/player_ready";
610 int f = entcs_GetClientColors(pl.sv_entnum);
612 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
613 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
614 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
615 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
616 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
619 return entcs_GetName(pl.sv_entnum);
622 string Scoreboard_GetField(entity pl, PlayerScoreField field)
624 float tmp, num, denom;
627 sbt_field_rgb = '1 1 1';
628 sbt_field_icon0 = "";
629 sbt_field_icon1 = "";
630 sbt_field_icon2 = "";
631 sbt_field_icon0_rgb = '1 1 1';
632 sbt_field_icon1_rgb = '1 1 1';
633 sbt_field_icon2_rgb = '1 1 1';
638 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
639 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
643 tmp = max(0, min(220, f-80)) / 220;
644 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
650 f = pl.ping_packetloss;
651 tmp = pl.ping_movementloss;
652 if(f == 0 && tmp == 0)
654 str = ftos(ceil(f * 100));
656 str = strcat(str, "~", ftos(ceil(tmp * 100)));
657 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
658 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
662 str = Scoreboard_GetName(pl);
663 if (autocvar_hud_panel_scoreboard_playerid)
664 str = Scoreboard_AddPlayerId(str, pl);
668 f = pl.(scores(SP_KILLS));
669 f -= pl.(scores(SP_SUICIDES));
673 num = pl.(scores(SP_KILLS));
674 denom = pl.(scores(SP_DEATHS));
677 sbt_field_rgb = '0 1 0';
678 str = sprintf("%d", num);
679 } else if(num <= 0) {
680 sbt_field_rgb = '1 0 0';
681 str = sprintf("%.1f", num/denom);
683 str = sprintf("%.1f", num/denom);
687 f = pl.(scores(SP_KILLS));
688 f -= pl.(scores(SP_DEATHS));
691 sbt_field_rgb = '0 1 0';
693 sbt_field_rgb = '1 1 1';
695 sbt_field_rgb = '1 0 0';
701 float elo = pl.(scores(SP_ELO));
703 case -1: return "...";
704 case -2: return _("N/A");
705 default: return ftos(elo);
711 float fps = pl.(scores(SP_FPS));
714 sbt_field_rgb = '1 1 1';
715 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
717 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
718 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
722 case SP_DMG: case SP_DMGTAKEN:
723 return sprintf("%.1f k", pl.(scores(field)) / 1000);
725 default: case SP_SCORE:
726 tmp = pl.(scores(field));
727 f = scores_flags(field);
728 if(field == ps_primary)
729 sbt_field_rgb = '1 1 0';
730 else if(field == ps_secondary)
731 sbt_field_rgb = '0 1 1';
733 sbt_field_rgb = '1 1 1';
734 return ScoreString(f, tmp);
739 float sbt_fixcolumnwidth_len;
740 float sbt_fixcolumnwidth_iconlen;
741 float sbt_fixcolumnwidth_marginlen;
743 string Scoreboard_FixColumnWidth(int i, string str)
749 sbt_fixcolumnwidth_iconlen = 0;
751 if(sbt_field_icon0 != "")
753 sz = draw_getimagesize(sbt_field_icon0);
755 if(sbt_fixcolumnwidth_iconlen < f)
756 sbt_fixcolumnwidth_iconlen = f;
759 if(sbt_field_icon1 != "")
761 sz = draw_getimagesize(sbt_field_icon1);
763 if(sbt_fixcolumnwidth_iconlen < f)
764 sbt_fixcolumnwidth_iconlen = f;
767 if(sbt_field_icon2 != "")
769 sz = draw_getimagesize(sbt_field_icon2);
771 if(sbt_fixcolumnwidth_iconlen < f)
772 sbt_fixcolumnwidth_iconlen = f;
775 if(sbt_fixcolumnwidth_iconlen != 0)
777 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
778 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
781 sbt_fixcolumnwidth_marginlen = 0;
783 if(sbt_field[i] == SP_NAME) // name gets all remaining space
786 float remaining_space = 0;
787 for(j = 0; j < sbt_num_fields; ++j)
789 if (sbt_field[i] != SP_SEPARATOR)
790 remaining_space += sbt_field_size[j] + hud_fontsize.x;
791 sbt_field_size[i] = panel_size.x - remaining_space;
793 if (sbt_fixcolumnwidth_iconlen != 0)
794 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
795 float namesize = panel_size.x - remaining_space;
796 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
797 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
799 max_namesize = vid_conwidth - remaining_space;
802 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
804 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
805 if(sbt_field_size[i] < f)
806 sbt_field_size[i] = f;
811 void Scoreboard_initFieldSizes()
813 for(int i = 0; i < sbt_num_fields; ++i)
815 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
816 Scoreboard_FixColumnWidth(i, "");
820 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
823 vector column_dim = eY * panel_size.y;
825 column_dim.y -= 1.25 * hud_fontsize.y;
826 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
827 pos.x += hud_fontsize.x * 0.5;
828 for(i = 0; i < sbt_num_fields; ++i)
830 if(sbt_field[i] == SP_SEPARATOR)
832 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
835 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
836 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
837 pos.x += column_dim.x;
839 if(sbt_field[i] == SP_SEPARATOR)
841 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
842 for(i = sbt_num_fields - 1; i > 0; --i)
844 if(sbt_field[i] == SP_SEPARATOR)
847 pos.x -= sbt_field_size[i];
852 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
853 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
856 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
857 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
858 pos.x -= hud_fontsize.x;
863 pos.y += 1.25 * hud_fontsize.y;
867 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
869 TC(bool, is_self); TC(int, pl_number);
871 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
873 vector h_pos = item_pos;
874 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
875 // alternated rows highlighting
877 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
878 else if((sbt_highlight) && (!(pl_number % 2)))
879 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
881 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
883 vector pos = item_pos;
884 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
886 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
888 pos.x += hud_fontsize.x * 0.5;
889 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
890 vector tmp = '0 0 0';
892 PlayerScoreField field;
893 for(i = 0; i < sbt_num_fields; ++i)
895 field = sbt_field[i];
896 if(field == SP_SEPARATOR)
899 if(is_spec && field != SP_NAME && field != SP_PING) {
900 pos.x += sbt_field_size[i] + hud_fontsize.x;
903 str = Scoreboard_GetField(pl, field);
904 str = Scoreboard_FixColumnWidth(i, str);
906 pos.x += sbt_field_size[i] + hud_fontsize.x;
908 if(field == SP_NAME) {
909 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
910 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
912 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
913 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
916 tmp.x = sbt_field_size[i] + hud_fontsize.x;
917 if(sbt_field_icon0 != "")
918 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
919 if(sbt_field_icon1 != "")
920 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
921 if(sbt_field_icon2 != "")
922 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
925 if(sbt_field[i] == SP_SEPARATOR)
927 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
928 for(i = sbt_num_fields-1; i > 0; --i)
930 field = sbt_field[i];
931 if(field == SP_SEPARATOR)
934 if(is_spec && field != SP_NAME && field != SP_PING) {
935 pos.x -= sbt_field_size[i] + hud_fontsize.x;
939 str = Scoreboard_GetField(pl, field);
940 str = Scoreboard_FixColumnWidth(i, str);
942 if(field == SP_NAME) {
943 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
944 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
946 tmp.x = sbt_fixcolumnwidth_len;
947 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
950 tmp.x = sbt_field_size[i];
951 if(sbt_field_icon0 != "")
952 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
953 if(sbt_field_icon1 != "")
954 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
955 if(sbt_field_icon2 != "")
956 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
957 pos.x -= sbt_field_size[i] + hud_fontsize.x;
962 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
965 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
968 vector h_pos = item_pos;
969 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
971 bool complete = (this_team == NUM_SPECTATOR);
974 if((sbt_highlight) && (!(pl_number % 2)))
975 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
977 vector pos = item_pos;
978 pos.x += hud_fontsize.x * 0.5;
979 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
981 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
983 width_limit -= stringwidth("...", false, hud_fontsize);
984 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
985 static float max_name_width = 0;
988 float min_fieldsize = 0;
989 float fieldpadding = hud_fontsize.x * 0.25;
990 if(this_team == NUM_SPECTATOR)
992 if(autocvar_hud_panel_scoreboard_spectators_showping)
993 min_fieldsize = stringwidth("999", false, hud_fontsize);
995 else if(autocvar_hud_panel_scoreboard_others_showscore)
996 min_fieldsize = stringwidth("99", false, hud_fontsize);
997 for(i = 0; pl; pl = pl.sort_next)
999 if(pl.team != this_team)
1001 if(pl == ignored_pl)
1005 if(this_team == NUM_SPECTATOR)
1007 if(autocvar_hud_panel_scoreboard_spectators_showping)
1008 field = Scoreboard_GetField(pl, SP_PING);
1010 else if(autocvar_hud_panel_scoreboard_others_showscore)
1011 field = Scoreboard_GetField(pl, SP_SCORE);
1013 string str = entcs_GetName(pl.sv_entnum);
1014 if (autocvar_hud_panel_scoreboard_playerid)
1015 str = Scoreboard_AddPlayerId(str, pl);
1016 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1017 float column_width = stringwidth(str, true, hud_fontsize);
1018 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1020 if(column_width > max_name_width)
1021 max_name_width = column_width;
1022 column_width = max_name_width;
1026 fieldsize = stringwidth(field, false, hud_fontsize);
1027 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1030 if(pos.x + column_width > width_limit)
1035 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1040 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1041 pos.y += hud_fontsize.y * 1.25;
1045 vector name_pos = pos;
1046 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1047 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1048 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1051 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1052 h_size.y = hud_fontsize.y;
1053 vector field_pos = pos;
1054 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1055 field_pos.x += column_width - h_size.x;
1057 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1058 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1059 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 h_size.x = column_width + hud_fontsize.x * 0.25;
1064 h_size.y = hud_fontsize.y;
1065 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1067 pos.x += column_width;
1068 pos.x += hud_fontsize.x;
1070 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1073 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1075 int max_players = 999;
1076 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1078 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1081 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1082 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1083 height /= team_count;
1086 height -= panel_bg_padding * 2; // - padding
1087 max_players = floor(height / (hud_fontsize.y * 1.25));
1088 if(max_players <= 1)
1090 if(max_players == tm.team_size)
1095 entity me = playerslots[current_player];
1097 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1098 panel_size.y += panel_bg_padding * 2;
1101 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1102 if(panel.current_panel_bg != "0")
1103 end_pos.y += panel_bg_border * 2;
1105 if(panel_bg_padding)
1107 panel_pos += '1 1 0' * panel_bg_padding;
1108 panel_size -= '2 2 0' * panel_bg_padding;
1112 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1116 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1118 pos.y += 1.25 * hud_fontsize.y;
1121 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1123 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1126 // print header row and highlight columns
1127 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1129 // fill the table and draw the rows
1130 bool is_self = false;
1131 bool self_shown = false;
1133 for(pl = players.sort_next; pl; pl = pl.sort_next)
1135 if(pl.team != tm.team)
1137 if(i == max_players - 2 && pl != me)
1139 if(!self_shown && me.team == tm.team)
1141 Scoreboard_DrawItem(pos, rgb, me, true, i);
1143 pos.y += 1.25 * hud_fontsize.y;
1147 if(i >= max_players - 1)
1149 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1152 is_self = (pl.sv_entnum == current_player);
1153 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1156 pos.y += 1.25 * hud_fontsize.y;
1160 panel_size.x += panel_bg_padding * 2; // restore initial width
1164 bool Scoreboard_WouldDraw()
1166 if (MUTATOR_CALLHOOK(DrawScoreboard))
1168 else if (QuickMenu_IsOpened())
1170 else if (HUD_Radar_Clickable())
1172 else if (scoreboard_showscores)
1174 else if (intermission == 1)
1176 else if (intermission == 2)
1178 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1179 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1183 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1188 float average_accuracy;
1189 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1191 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1193 WepSet weapons_stat = WepSet_GetFromStat();
1194 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1195 int disownedcnt = 0;
1197 FOREACH(Weapons, it != WEP_Null, {
1198 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1200 WepSet set = it.m_wepset;
1201 if(it.spawnflags & WEP_TYPE_OTHER)
1206 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1208 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1215 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1216 if (weapon_cnt <= 0) return pos;
1219 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1221 int columnns = ceil(weapon_cnt / rows);
1223 float weapon_height = 29;
1224 float height = hud_fontsize.y + weapon_height;
1226 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);
1227 pos.y += 1.25 * hud_fontsize.y;
1228 if(panel.current_panel_bg != "0")
1229 pos.y += panel_bg_border;
1232 panel_size.y = height * rows;
1233 panel_size.y += panel_bg_padding * 2;
1235 float panel_bg_alpha_save = panel_bg_alpha;
1236 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1238 panel_bg_alpha = panel_bg_alpha_save;
1240 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1241 if(panel.current_panel_bg != "0")
1242 end_pos.y += panel_bg_border * 2;
1244 if(panel_bg_padding)
1246 panel_pos += '1 1 0' * panel_bg_padding;
1247 panel_size -= '2 2 0' * panel_bg_padding;
1251 vector tmp = panel_size;
1253 float weapon_width = tmp.x / columnns / rows;
1256 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1260 // column highlighting
1261 for (int i = 0; i < columnns; ++i)
1263 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);
1266 for (int i = 0; i < rows; ++i)
1267 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1270 average_accuracy = 0;
1271 int weapons_with_stats = 0;
1273 pos.x += weapon_width / 2;
1275 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1278 Accuracy_LoadColors();
1280 float oldposx = pos.x;
1284 FOREACH(Weapons, it != WEP_Null, {
1285 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1287 WepSet set = it.m_wepset;
1288 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1290 if (it.spawnflags & WEP_TYPE_OTHER)
1294 if (weapon_stats >= 0)
1295 weapon_alpha = sbt_fg_alpha;
1297 weapon_alpha = 0.2 * sbt_fg_alpha;
1300 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1302 if (weapon_stats >= 0) {
1303 weapons_with_stats += 1;
1304 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1307 s = sprintf("%d%%", weapon_stats * 100);
1310 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1312 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1313 rgb = Accuracy_GetColor(weapon_stats);
1315 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1317 tmpos.x += weapon_width * rows;
1318 pos.x += weapon_width * rows;
1319 if (rows == 2 && column == columnns - 1) {
1327 if (weapons_with_stats)
1328 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1330 panel_size.x += panel_bg_padding * 2; // restore initial width
1335 .bool uninteresting;
1336 STATIC_INIT(default_order_items_label)
1338 IL_EACH(default_order_items, true, {
1339 if(!(it.instanceOfPowerup
1340 || it == ITEM_HealthMega || it == ITEM_HealthBig
1341 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1344 it.uninteresting = true;
1349 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1351 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1353 int disowned_cnt = 0;
1354 int uninteresting_cnt = 0;
1355 IL_EACH(default_order_items, true, {
1356 int q = g_inventory.inv_items[it.m_id];
1357 //q = 1; // debug: display all items
1358 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1359 ++uninteresting_cnt;
1363 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1364 int n = items_cnt - disowned_cnt;
1365 if (n <= 0) return pos;
1367 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1368 int columnns = max(6, ceil(n / rows));
1371 float fontsize = height * 1/3;
1372 float item_height = height * 2/3;
1374 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1375 pos.y += 1.25 * hud_fontsize.y;
1376 if(panel.current_panel_bg != "0")
1377 pos.y += panel_bg_border;
1380 panel_size.y = height * rows;
1381 panel_size.y += panel_bg_padding * 2;
1383 float panel_bg_alpha_save = panel_bg_alpha;
1384 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1386 panel_bg_alpha = panel_bg_alpha_save;
1388 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1389 if(panel.current_panel_bg != "0")
1390 end_pos.y += panel_bg_border * 2;
1392 if(panel_bg_padding)
1394 panel_pos += '1 1 0' * panel_bg_padding;
1395 panel_size -= '2 2 0' * panel_bg_padding;
1399 vector tmp = panel_size;
1401 float item_width = tmp.x / columnns / rows;
1404 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1408 // column highlighting
1409 for (int i = 0; i < columnns; ++i)
1411 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1414 for (int i = 0; i < rows; ++i)
1415 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1419 pos.x += item_width / 2;
1421 float oldposx = pos.x;
1425 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1426 int n = g_inventory.inv_items[it.m_id];
1427 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1428 if (n <= 0) continue;
1429 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1431 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1432 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1433 tmpos.x += item_width * rows;
1434 pos.x += item_width * rows;
1435 if (rows == 2 && column == columnns - 1) {
1443 panel_size.x += panel_bg_padding * 2; // restore initial width
1448 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1450 pos.x += hud_fontsize.x * 0.25;
1451 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1453 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1455 pos.y += hud_fontsize.y;
1460 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1461 float stat_secrets_found, stat_secrets_total;
1462 float stat_monsters_killed, stat_monsters_total;
1466 // get monster stats
1467 stat_monsters_killed = STAT(MONSTERS_KILLED);
1468 stat_monsters_total = STAT(MONSTERS_TOTAL);
1470 // get secrets stats
1471 stat_secrets_found = STAT(SECRETS_FOUND);
1472 stat_secrets_total = STAT(SECRETS_TOTAL);
1474 // get number of rows
1475 if(stat_secrets_total)
1477 if(stat_monsters_total)
1480 // if no rows, return
1484 // draw table header
1485 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1486 pos.y += 1.25 * hud_fontsize.y;
1487 if(panel.current_panel_bg != "0")
1488 pos.y += panel_bg_border;
1491 panel_size.y = hud_fontsize.y * rows;
1492 panel_size.y += panel_bg_padding * 2;
1495 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1496 if(panel.current_panel_bg != "0")
1497 end_pos.y += panel_bg_border * 2;
1499 if(panel_bg_padding)
1501 panel_pos += '1 1 0' * panel_bg_padding;
1502 panel_size -= '2 2 0' * panel_bg_padding;
1506 vector tmp = panel_size;
1509 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1512 if(stat_monsters_total)
1514 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1515 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1519 if(stat_secrets_total)
1521 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1522 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1525 panel_size.x += panel_bg_padding * 2; // restore initial width
1530 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1533 RANKINGS_RECEIVED_CNT = 0;
1534 for (i=RANKINGS_CNT-1; i>=0; --i)
1536 ++RANKINGS_RECEIVED_CNT;
1538 if (RANKINGS_RECEIVED_CNT == 0)
1541 vector hl_rgb = rgb + '0.5 0.5 0.5';
1543 pos.y += hud_fontsize.y;
1544 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1545 pos.y += 1.25 * hud_fontsize.y;
1546 if(panel.current_panel_bg != "0")
1547 pos.y += panel_bg_border;
1552 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1554 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1559 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1561 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1565 float ranksize = 3 * hud_fontsize.x;
1566 float timesize = 5 * hud_fontsize.x;
1567 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1568 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1569 columns = min(columns, RANKINGS_RECEIVED_CNT);
1571 // expand name column to fill the entire row
1572 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1573 namesize += available_space;
1574 columnsize.x += available_space;
1576 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1577 panel_size.y += panel_bg_padding * 2;
1581 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1582 if(panel.current_panel_bg != "0")
1583 end_pos.y += panel_bg_border * 2;
1585 if(panel_bg_padding)
1587 panel_pos += '1 1 0' * panel_bg_padding;
1588 panel_size -= '2 2 0' * panel_bg_padding;
1594 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1596 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1598 int column = 0, j = 0;
1599 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1600 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1607 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1608 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1609 else if(!((j + column) & 1) && sbt_highlight)
1610 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1612 str = count_ordinal(i+1);
1613 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1614 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1615 str = ColorTranslateRGB(grecordholder[i]);
1617 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1618 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1620 pos.y += 1.25 * hud_fontsize.y;
1622 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1626 pos.x += panel_size.x / columns;
1627 pos.y = panel_pos.y;
1630 strfree(zoned_name_self);
1632 panel_size.x += panel_bg_padding * 2; // restore initial width
1636 float scoreboard_time;
1637 bool have_weapon_stats;
1638 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1640 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1642 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1645 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1646 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1652 if (!have_weapon_stats)
1654 FOREACH(Weapons, it != WEP_Null, {
1655 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1656 if (weapon_stats >= 0)
1658 have_weapon_stats = true;
1662 if (!have_weapon_stats)
1669 bool have_item_stats;
1670 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1672 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1674 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1677 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1678 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1684 if (!have_item_stats)
1686 IL_EACH(default_order_items, true, {
1687 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1689 int q = g_inventory.inv_items[it.m_id];
1690 //q = 1; // debug: display all items
1693 have_item_stats = true;
1698 if (!have_item_stats)
1705 void Scoreboard_Draw()
1707 if(!autocvar__hud_configure)
1709 if(!hud_draw_maximized) return;
1711 // frametime checks allow to toggle the scoreboard even when the game is paused
1712 if(scoreboard_active) {
1713 if (scoreboard_fade_alpha == 0)
1714 scoreboard_time = time;
1715 if(hud_configure_menu_open == 1)
1716 scoreboard_fade_alpha = 1;
1717 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1718 if (scoreboard_fadeinspeed && frametime)
1719 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1721 scoreboard_fade_alpha = 1;
1722 if(hud_fontsize_str != autocvar_hud_fontsize)
1724 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1725 Scoreboard_initFieldSizes();
1726 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1730 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1731 if (scoreboard_fadeoutspeed && frametime)
1732 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1734 scoreboard_fade_alpha = 0;
1737 if (!scoreboard_fade_alpha)
1739 scoreboard_acc_fade_alpha = 0;
1740 scoreboard_itemstats_fade_alpha = 0;
1745 scoreboard_fade_alpha = 0;
1747 if (autocvar_hud_panel_scoreboard_dynamichud)
1750 HUD_Scale_Disable();
1752 if(scoreboard_fade_alpha <= 0)
1754 panel_fade_alpha *= scoreboard_fade_alpha;
1755 HUD_Panel_LoadCvars();
1757 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1758 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1759 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1760 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1761 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1762 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1763 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1765 // don't overlap with con_notify
1766 if(!autocvar__hud_configure)
1767 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1769 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1770 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1771 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1772 panel_size.x = fixed_scoreboard_width;
1774 Scoreboard_UpdatePlayerTeams();
1776 float initial_pos_y = panel_pos.y;
1777 vector pos = panel_pos;
1782 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1784 // Begin of Game Info Section
1785 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1786 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1788 // Game Info: Game Type
1789 str = MapInfo_Type_ToText(gametype);
1790 draw_beginBoldFont();
1791 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);
1794 // Game Info: Game Detail
1795 float tl = STAT(TIMELIMIT);
1796 float fl = STAT(FRAGLIMIT);
1797 float ll = STAT(LEADLIMIT);
1798 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1801 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1802 if(!gametype.m_hidelimits)
1807 str = strcat(str, "^7 / "); // delimiter
1810 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1811 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1812 (teamscores_label(ts_primary) == "fastest") ? "" :
1813 TranslateScoresLabel(teamscores_label(ts_primary))));
1817 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1818 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1819 (scores_label(ps_primary) == "fastest") ? "" :
1820 TranslateScoresLabel(scores_label(ps_primary))));
1825 if(tl > 0 || fl > 0)
1828 if (ll_and_fl && fl > 0)
1829 str = strcat(str, "^7 & ");
1831 str = strcat(str, "^7 / ");
1836 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1837 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1838 (teamscores_label(ts_primary) == "fastest") ? "" :
1839 TranslateScoresLabel(teamscores_label(ts_primary))));
1843 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1844 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1845 (scores_label(ps_primary) == "fastest") ? "" :
1846 TranslateScoresLabel(scores_label(ps_primary))));
1851 pos.y += sb_gameinfo_type_fontsize.y;
1852 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
1854 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1855 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1856 // End of Game Info Section
1858 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1859 if(panel.current_panel_bg != "0")
1860 pos.y += panel_bg_border;
1862 // Draw the scoreboard
1863 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1866 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1870 vector panel_bg_color_save = panel_bg_color;
1871 vector team_score_baseoffset;
1872 vector team_size_baseoffset;
1873 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1875 // put team score to the left of scoreboard (and team size to the right)
1876 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1877 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1878 if(panel.current_panel_bg != "0")
1880 team_score_baseoffset.x -= panel_bg_border;
1881 team_size_baseoffset.x += panel_bg_border;
1886 // put team score to the right of scoreboard (and team size to the left)
1887 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1888 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1889 if(panel.current_panel_bg != "0")
1891 team_score_baseoffset.x += panel_bg_border;
1892 team_size_baseoffset.x -= panel_bg_border;
1896 int team_size_total = 0;
1897 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1899 // calculate team size total (sum of all team sizes)
1900 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1901 if(tm.team != NUM_SPECTATOR)
1902 team_size_total += tm.team_size;
1905 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1907 if(tm.team == NUM_SPECTATOR)
1912 draw_beginBoldFont();
1913 vector rgb = Team_ColorRGB(tm.team);
1914 str = ftos(tm.(teamscores(ts_primary)));
1915 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1917 // team score on the left (default)
1918 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1922 // team score on the right
1923 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1925 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1927 // team size (if set to show on the side)
1928 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1930 // calculate the starting position for the whole team size info string
1931 str = sprintf("%d/%d", tm.team_size, team_size_total);
1932 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1934 // team size on the left
1935 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1939 // team size on the right
1940 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1942 str = sprintf("%d", tm.team_size);
1943 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1944 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1945 str = sprintf("/%d", team_size_total);
1946 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1950 // secondary score, e.g. keyhunt
1951 if(ts_primary != ts_secondary)
1953 str = ftos(tm.(teamscores(ts_secondary)));
1954 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1957 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1962 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1965 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1968 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1969 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1970 else if(panel_bg_color_team > 0)
1971 panel_bg_color = rgb * panel_bg_color_team;
1973 panel_bg_color = rgb;
1974 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1976 panel_bg_color = panel_bg_color_save;
1980 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1981 if(tm.team != NUM_SPECTATOR)
1984 // display it anyway
1985 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1988 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1989 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1990 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1991 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1993 if(MUTATOR_CALLHOOK(ShowRankings)) {
1994 string ranktitle = M_ARGV(0, string);
1995 if(race_speedaward) {
1996 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);
1997 pos.y += 1.25 * hud_fontsize.y;
1999 if(race_speedaward_alltimebest) {
2000 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);
2001 pos.y += 1.25 * hud_fontsize.y;
2003 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2006 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2009 for(pl = players.sort_next; pl; pl = pl.sort_next)
2011 if(pl.team == NUM_SPECTATOR)
2013 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2014 if(tm.team == NUM_SPECTATOR)
2016 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2017 draw_beginBoldFont();
2018 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2020 pos.y += 1.25 * hud_fontsize.y;
2022 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2023 pos.y += 1.25 * hud_fontsize.y;
2030 // print information about respawn status
2031 float respawn_time = STAT(RESPAWN_TIME);
2035 if(respawn_time < 0)
2037 // a negative number means we are awaiting respawn, time value is still the same
2038 respawn_time *= -1; // remove mark now that we checked it
2040 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2041 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2043 str = sprintf(_("^1Respawning in ^3%s^1..."),
2044 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2045 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2047 count_seconds(ceil(respawn_time - time))
2051 else if(time < respawn_time)
2053 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2054 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2055 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2057 count_seconds(ceil(respawn_time - time))
2061 else if(time >= respawn_time)
2062 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2064 pos.y += 1.2 * hud_fontsize.y;
2065 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2068 pos.y += 2 * hud_fontsize.y;
2069 if (scoreboard_fade_alpha < 1)
2070 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2071 else if (pos.y != scoreboard_bottom)
2073 if (pos.y > scoreboard_bottom)
2074 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2076 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));