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 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
467 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
468 str = strtolower(str);
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;
482 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
485 FOREACH(Scores, true, {
486 if (str == strtolower(scores_label(it))) {
488 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
498 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
502 sbt_field[sbt_num_fields] = j;
505 if(j == ps_secondary)
506 have_secondary = true;
511 if(sbt_num_fields >= MAX_SBT_FIELDS)
515 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
517 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
518 have_secondary = true;
519 if(ps_primary == ps_secondary)
520 have_secondary = true;
521 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
523 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
527 strunzone(sbt_field_title[sbt_num_fields]);
528 for(i = sbt_num_fields; i > 0; --i)
530 sbt_field_title[i] = sbt_field_title[i-1];
531 sbt_field_size[i] = sbt_field_size[i-1];
532 sbt_field[i] = sbt_field[i-1];
534 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
535 sbt_field[0] = SP_NAME;
537 LOG_INFO("fixed missing field 'name'");
541 strunzone(sbt_field_title[sbt_num_fields]);
542 for(i = sbt_num_fields; i > 1; --i)
544 sbt_field_title[i] = sbt_field_title[i-1];
545 sbt_field_size[i] = sbt_field_size[i-1];
546 sbt_field[i] = sbt_field[i-1];
548 sbt_field_title[1] = strzone("|");
549 sbt_field[1] = SP_SEPARATOR;
550 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
552 LOG_INFO("fixed missing field '|'");
555 else if(!have_separator)
557 strcpy(sbt_field_title[sbt_num_fields], "|");
558 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
559 sbt_field[sbt_num_fields] = SP_SEPARATOR;
561 LOG_INFO("fixed missing field '|'");
565 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
566 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
567 sbt_field[sbt_num_fields] = ps_secondary;
569 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
573 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
574 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
575 sbt_field[sbt_num_fields] = ps_primary;
577 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
581 sbt_field[sbt_num_fields] = SP_END;
584 string Scoreboard_AddPlayerId(string pl_name, entity pl)
586 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
587 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
588 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
592 vector sbt_field_rgb;
593 string sbt_field_icon0;
594 string sbt_field_icon1;
595 string sbt_field_icon2;
596 vector sbt_field_icon0_rgb;
597 vector sbt_field_icon1_rgb;
598 vector sbt_field_icon2_rgb;
599 string Scoreboard_GetName(entity pl)
601 if(ready_waiting && pl.ready)
603 sbt_field_icon0 = "gfx/scoreboard/player_ready";
607 int f = entcs_GetClientColors(pl.sv_entnum);
609 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
610 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
611 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
612 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
613 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
616 return entcs_GetName(pl.sv_entnum);
619 string Scoreboard_GetField(entity pl, PlayerScoreField field)
621 float tmp, num, denom;
624 sbt_field_rgb = '1 1 1';
625 sbt_field_icon0 = "";
626 sbt_field_icon1 = "";
627 sbt_field_icon2 = "";
628 sbt_field_icon0_rgb = '1 1 1';
629 sbt_field_icon1_rgb = '1 1 1';
630 sbt_field_icon2_rgb = '1 1 1';
635 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
636 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
640 tmp = max(0, min(220, f-80)) / 220;
641 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
647 f = pl.ping_packetloss;
648 tmp = pl.ping_movementloss;
649 if(f == 0 && tmp == 0)
651 str = ftos(ceil(f * 100));
653 str = strcat(str, "~", ftos(ceil(tmp * 100)));
654 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
655 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
659 str = Scoreboard_GetName(pl);
660 if (autocvar_hud_panel_scoreboard_playerid)
661 str = Scoreboard_AddPlayerId(str, pl);
665 f = pl.(scores(SP_KILLS));
666 f -= pl.(scores(SP_SUICIDES));
670 num = pl.(scores(SP_KILLS));
671 denom = pl.(scores(SP_DEATHS));
674 sbt_field_rgb = '0 1 0';
675 str = sprintf("%d", num);
676 } else if(num <= 0) {
677 sbt_field_rgb = '1 0 0';
678 str = sprintf("%.1f", num/denom);
680 str = sprintf("%.1f", num/denom);
684 f = pl.(scores(SP_KILLS));
685 f -= pl.(scores(SP_DEATHS));
688 sbt_field_rgb = '0 1 0';
690 sbt_field_rgb = '1 1 1';
692 sbt_field_rgb = '1 0 0';
698 float elo = pl.(scores(SP_ELO));
700 case -1: return "...";
701 case -2: return _("N/A");
702 default: return ftos(elo);
708 float fps = pl.(scores(SP_FPS));
711 sbt_field_rgb = '1 1 1';
712 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
714 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
715 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
719 case SP_DMG: case SP_DMGTAKEN:
720 return sprintf("%.1f k", pl.(scores(field)) / 1000);
722 default: case SP_SCORE:
723 tmp = pl.(scores(field));
724 f = scores_flags(field);
725 if(field == ps_primary)
726 sbt_field_rgb = '1 1 0';
727 else if(field == ps_secondary)
728 sbt_field_rgb = '0 1 1';
730 sbt_field_rgb = '1 1 1';
731 return ScoreString(f, tmp);
736 float sbt_fixcolumnwidth_len;
737 float sbt_fixcolumnwidth_iconlen;
738 float sbt_fixcolumnwidth_marginlen;
740 string Scoreboard_FixColumnWidth(int i, string str)
746 sbt_fixcolumnwidth_iconlen = 0;
748 if(sbt_field_icon0 != "")
750 sz = draw_getimagesize(sbt_field_icon0);
752 if(sbt_fixcolumnwidth_iconlen < f)
753 sbt_fixcolumnwidth_iconlen = f;
756 if(sbt_field_icon1 != "")
758 sz = draw_getimagesize(sbt_field_icon1);
760 if(sbt_fixcolumnwidth_iconlen < f)
761 sbt_fixcolumnwidth_iconlen = f;
764 if(sbt_field_icon2 != "")
766 sz = draw_getimagesize(sbt_field_icon2);
768 if(sbt_fixcolumnwidth_iconlen < f)
769 sbt_fixcolumnwidth_iconlen = f;
772 if(sbt_fixcolumnwidth_iconlen != 0)
774 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
775 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
778 sbt_fixcolumnwidth_marginlen = 0;
780 if(sbt_field[i] == SP_NAME) // name gets all remaining space
783 float remaining_space = 0;
784 for(j = 0; j < sbt_num_fields; ++j)
786 if (sbt_field[i] != SP_SEPARATOR)
787 remaining_space += sbt_field_size[j] + hud_fontsize.x;
788 sbt_field_size[i] = panel_size.x - remaining_space;
790 if (sbt_fixcolumnwidth_iconlen != 0)
791 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
792 float namesize = panel_size.x - remaining_space;
793 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
794 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
796 max_namesize = vid_conwidth - remaining_space;
799 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
801 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
802 if(sbt_field_size[i] < f)
803 sbt_field_size[i] = f;
808 void Scoreboard_initFieldSizes()
810 for(int i = 0; i < sbt_num_fields; ++i)
812 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
813 Scoreboard_FixColumnWidth(i, "");
817 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
820 vector column_dim = eY * panel_size.y;
822 column_dim.y -= 1.25 * hud_fontsize.y;
823 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
824 pos.x += hud_fontsize.x * 0.5;
825 for(i = 0; i < sbt_num_fields; ++i)
827 if(sbt_field[i] == SP_SEPARATOR)
829 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
832 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
833 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
834 pos.x += column_dim.x;
836 if(sbt_field[i] == SP_SEPARATOR)
838 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
839 for(i = sbt_num_fields - 1; i > 0; --i)
841 if(sbt_field[i] == SP_SEPARATOR)
844 pos.x -= sbt_field_size[i];
849 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
850 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
853 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
854 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
855 pos.x -= hud_fontsize.x;
860 pos.y += 1.25 * hud_fontsize.y;
864 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
866 TC(bool, is_self); TC(int, pl_number);
868 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
870 vector h_pos = item_pos;
871 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
872 // alternated rows highlighting
874 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
875 else if((sbt_highlight) && (!(pl_number % 2)))
876 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
878 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
880 vector pos = item_pos;
881 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
883 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
885 pos.x += hud_fontsize.x * 0.5;
886 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
887 vector tmp = '0 0 0';
889 PlayerScoreField field;
890 for(i = 0; i < sbt_num_fields; ++i)
892 field = sbt_field[i];
893 if(field == SP_SEPARATOR)
896 if(is_spec && field != SP_NAME && field != SP_PING) {
897 pos.x += sbt_field_size[i] + hud_fontsize.x;
900 str = Scoreboard_GetField(pl, field);
901 str = Scoreboard_FixColumnWidth(i, str);
903 pos.x += sbt_field_size[i] + hud_fontsize.x;
905 if(field == SP_NAME) {
906 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
907 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
909 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
910 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
913 tmp.x = sbt_field_size[i] + hud_fontsize.x;
914 if(sbt_field_icon0 != "")
915 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
916 if(sbt_field_icon1 != "")
917 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 if(sbt_field_icon2 != "")
919 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
922 if(sbt_field[i] == SP_SEPARATOR)
924 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
925 for(i = sbt_num_fields-1; i > 0; --i)
927 field = sbt_field[i];
928 if(field == SP_SEPARATOR)
931 if(is_spec && field != SP_NAME && field != SP_PING) {
932 pos.x -= sbt_field_size[i] + hud_fontsize.x;
936 str = Scoreboard_GetField(pl, field);
937 str = Scoreboard_FixColumnWidth(i, str);
939 if(field == SP_NAME) {
940 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
941 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
943 tmp.x = sbt_fixcolumnwidth_len;
944 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 tmp.x = sbt_field_size[i];
948 if(sbt_field_icon0 != "")
949 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
950 if(sbt_field_icon1 != "")
951 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
952 if(sbt_field_icon2 != "")
953 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
954 pos.x -= sbt_field_size[i] + hud_fontsize.x;
959 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
962 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
965 vector h_pos = item_pos;
966 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
968 bool complete = (this_team == NUM_SPECTATOR);
971 if((sbt_highlight) && (!(pl_number % 2)))
972 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
974 vector pos = item_pos;
975 pos.x += hud_fontsize.x * 0.5;
976 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
978 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
980 width_limit -= stringwidth("...", false, hud_fontsize);
981 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
982 static float max_name_width = 0;
985 float min_fieldsize = 0;
986 float fieldpadding = hud_fontsize.x * 0.25;
987 if(this_team == NUM_SPECTATOR)
989 if(autocvar_hud_panel_scoreboard_spectators_showping)
990 min_fieldsize = stringwidth("999", false, hud_fontsize);
992 else if(autocvar_hud_panel_scoreboard_others_showscore)
993 min_fieldsize = stringwidth("99", false, hud_fontsize);
994 for(i = 0; pl; pl = pl.sort_next)
996 if(pl.team != this_team)
1002 if(this_team == NUM_SPECTATOR)
1004 if(autocvar_hud_panel_scoreboard_spectators_showping)
1005 field = Scoreboard_GetField(pl, SP_PING);
1007 else if(autocvar_hud_panel_scoreboard_others_showscore)
1008 field = Scoreboard_GetField(pl, SP_SCORE);
1010 string str = entcs_GetName(pl.sv_entnum);
1011 if (autocvar_hud_panel_scoreboard_playerid)
1012 str = Scoreboard_AddPlayerId(str, pl);
1013 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1014 float column_width = stringwidth(str, true, hud_fontsize);
1015 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1017 if(column_width > max_name_width)
1018 max_name_width = column_width;
1019 column_width = max_name_width;
1023 fieldsize = stringwidth(field, false, hud_fontsize);
1024 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1027 if(pos.x + column_width > width_limit)
1032 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1037 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1038 pos.y += hud_fontsize.y * 1.25;
1042 vector name_pos = pos;
1043 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1044 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1045 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1048 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1049 h_size.y = hud_fontsize.y;
1050 vector field_pos = pos;
1051 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1052 field_pos.x += column_width - h_size.x;
1054 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1055 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1056 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1060 h_size.x = column_width + hud_fontsize.x * 0.25;
1061 h_size.y = hud_fontsize.y;
1062 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1064 pos.x += column_width;
1065 pos.x += hud_fontsize.x;
1067 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1070 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1072 int max_players = 999;
1073 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1075 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1078 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1079 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1080 height /= team_count;
1083 height -= panel_bg_padding * 2; // - padding
1084 max_players = floor(height / (hud_fontsize.y * 1.25));
1085 if(max_players <= 1)
1087 if(max_players == tm.team_size)
1092 entity me = playerslots[current_player];
1094 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1095 panel_size.y += panel_bg_padding * 2;
1098 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1099 if(panel.current_panel_bg != "0")
1100 end_pos.y += panel_bg_border * 2;
1102 if(panel_bg_padding)
1104 panel_pos += '1 1 0' * panel_bg_padding;
1105 panel_size -= '2 2 0' * panel_bg_padding;
1109 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1113 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1115 pos.y += 1.25 * hud_fontsize.y;
1118 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1120 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1123 // print header row and highlight columns
1124 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1126 // fill the table and draw the rows
1127 bool is_self = false;
1128 bool self_shown = false;
1130 for(pl = players.sort_next; pl; pl = pl.sort_next)
1132 if(pl.team != tm.team)
1134 if(i == max_players - 2 && pl != me)
1136 if(!self_shown && me.team == tm.team)
1138 Scoreboard_DrawItem(pos, rgb, me, true, i);
1140 pos.y += 1.25 * hud_fontsize.y;
1144 if(i >= max_players - 1)
1146 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1149 is_self = (pl.sv_entnum == current_player);
1150 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1153 pos.y += 1.25 * hud_fontsize.y;
1157 panel_size.x += panel_bg_padding * 2; // restore initial width
1161 bool Scoreboard_WouldDraw()
1163 if (MUTATOR_CALLHOOK(DrawScoreboard))
1165 else if (QuickMenu_IsOpened())
1167 else if (HUD_Radar_Clickable())
1169 else if (scoreboard_showscores)
1171 else if (intermission == 1)
1173 else if (intermission == 2)
1175 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1176 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1180 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1185 float average_accuracy;
1186 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1188 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1190 WepSet weapons_stat = WepSet_GetFromStat();
1191 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1192 int disownedcnt = 0;
1194 FOREACH(Weapons, it != WEP_Null, {
1195 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1197 WepSet set = it.m_wepset;
1198 if(it.spawnflags & WEP_TYPE_OTHER)
1203 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1205 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1212 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1213 if (weapon_cnt <= 0) return pos;
1216 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1218 int columnns = ceil(weapon_cnt / rows);
1220 float weapon_height = 29;
1221 float height = hud_fontsize.y + weapon_height;
1223 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1224 pos.y += 1.25 * hud_fontsize.y;
1225 if(panel.current_panel_bg != "0")
1226 pos.y += panel_bg_border;
1229 panel_size.y = height * rows;
1230 panel_size.y += panel_bg_padding * 2;
1232 float panel_bg_alpha_save = panel_bg_alpha;
1233 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1235 panel_bg_alpha = panel_bg_alpha_save;
1237 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1238 if(panel.current_panel_bg != "0")
1239 end_pos.y += panel_bg_border * 2;
1241 if(panel_bg_padding)
1243 panel_pos += '1 1 0' * panel_bg_padding;
1244 panel_size -= '2 2 0' * panel_bg_padding;
1248 vector tmp = panel_size;
1250 float weapon_width = tmp.x / columnns / rows;
1253 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1257 // column highlighting
1258 for (int i = 0; i < columnns; ++i)
1260 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1263 for (int i = 0; i < rows; ++i)
1264 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1267 average_accuracy = 0;
1268 int weapons_with_stats = 0;
1270 pos.x += weapon_width / 2;
1272 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1275 Accuracy_LoadColors();
1277 float oldposx = pos.x;
1281 FOREACH(Weapons, it != WEP_Null, {
1282 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1284 WepSet set = it.m_wepset;
1285 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1287 if (it.spawnflags & WEP_TYPE_OTHER)
1291 if (weapon_stats >= 0)
1292 weapon_alpha = sbt_fg_alpha;
1294 weapon_alpha = 0.2 * sbt_fg_alpha;
1297 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1299 if (weapon_stats >= 0) {
1300 weapons_with_stats += 1;
1301 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1304 s = sprintf("%d%%", weapon_stats * 100);
1307 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1309 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1310 rgb = Accuracy_GetColor(weapon_stats);
1312 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1314 tmpos.x += weapon_width * rows;
1315 pos.x += weapon_width * rows;
1316 if (rows == 2 && column == columnns - 1) {
1324 if (weapons_with_stats)
1325 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1327 panel_size.x += panel_bg_padding * 2; // restore initial width
1332 .bool uninteresting;
1333 STATIC_INIT(default_order_items_label)
1335 IL_EACH(default_order_items, true, {
1336 if(!(it.instanceOfPowerup
1337 || it == ITEM_HealthMega || it == ITEM_HealthBig
1338 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1341 it.uninteresting = true;
1346 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1348 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1350 int disowned_cnt = 0;
1351 int uninteresting_cnt = 0;
1352 IL_EACH(default_order_items, true, {
1353 int q = g_inventory.inv_items[it.m_id];
1354 //q = 1; // debug: display all items
1355 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1356 ++uninteresting_cnt;
1360 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1361 int n = items_cnt - disowned_cnt;
1362 if (n <= 0) return pos;
1364 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1365 int columnns = max(6, ceil(n / rows));
1368 float fontsize = height * 1/3;
1369 float item_height = height * 2/3;
1371 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1372 pos.y += 1.25 * hud_fontsize.y;
1373 if(panel.current_panel_bg != "0")
1374 pos.y += panel_bg_border;
1377 panel_size.y = height * rows;
1378 panel_size.y += panel_bg_padding * 2;
1380 float panel_bg_alpha_save = panel_bg_alpha;
1381 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1383 panel_bg_alpha = panel_bg_alpha_save;
1385 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1386 if(panel.current_panel_bg != "0")
1387 end_pos.y += panel_bg_border * 2;
1389 if(panel_bg_padding)
1391 panel_pos += '1 1 0' * panel_bg_padding;
1392 panel_size -= '2 2 0' * panel_bg_padding;
1396 vector tmp = panel_size;
1398 float item_width = tmp.x / columnns / rows;
1401 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1405 // column highlighting
1406 for (int i = 0; i < columnns; ++i)
1408 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);
1411 for (int i = 0; i < rows; ++i)
1412 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);
1416 pos.x += item_width / 2;
1418 float oldposx = pos.x;
1422 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1423 int n = g_inventory.inv_items[it.m_id];
1424 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1425 if (n <= 0) continue;
1426 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);
1428 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1429 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);
1430 tmpos.x += item_width * rows;
1431 pos.x += item_width * rows;
1432 if (rows == 2 && column == columnns - 1) {
1440 panel_size.x += panel_bg_padding * 2; // restore initial width
1445 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1447 pos.x += hud_fontsize.x * 0.25;
1448 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1449 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1450 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 pos.y += hud_fontsize.y;
1457 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1458 float stat_secrets_found, stat_secrets_total;
1459 float stat_monsters_killed, stat_monsters_total;
1463 // get monster stats
1464 stat_monsters_killed = STAT(MONSTERS_KILLED);
1465 stat_monsters_total = STAT(MONSTERS_TOTAL);
1467 // get secrets stats
1468 stat_secrets_found = STAT(SECRETS_FOUND);
1469 stat_secrets_total = STAT(SECRETS_TOTAL);
1471 // get number of rows
1472 if(stat_secrets_total)
1474 if(stat_monsters_total)
1477 // if no rows, return
1481 // draw table header
1482 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1483 pos.y += 1.25 * hud_fontsize.y;
1484 if(panel.current_panel_bg != "0")
1485 pos.y += panel_bg_border;
1488 panel_size.y = hud_fontsize.y * rows;
1489 panel_size.y += panel_bg_padding * 2;
1492 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1493 if(panel.current_panel_bg != "0")
1494 end_pos.y += panel_bg_border * 2;
1496 if(panel_bg_padding)
1498 panel_pos += '1 1 0' * panel_bg_padding;
1499 panel_size -= '2 2 0' * panel_bg_padding;
1503 vector tmp = panel_size;
1506 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1509 if(stat_monsters_total)
1511 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1512 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1516 if(stat_secrets_total)
1518 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1519 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1522 panel_size.x += panel_bg_padding * 2; // restore initial width
1527 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1530 RANKINGS_RECEIVED_CNT = 0;
1531 for (i=RANKINGS_CNT-1; i>=0; --i)
1533 ++RANKINGS_RECEIVED_CNT;
1535 if (RANKINGS_RECEIVED_CNT == 0)
1538 vector hl_rgb = rgb + '0.5 0.5 0.5';
1540 pos.y += hud_fontsize.y;
1541 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1542 pos.y += 1.25 * hud_fontsize.y;
1543 if(panel.current_panel_bg != "0")
1544 pos.y += panel_bg_border;
1549 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1551 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1556 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1558 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1562 float ranksize = 3 * hud_fontsize.x;
1563 float timesize = 5 * hud_fontsize.x;
1564 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1565 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1566 columns = min(columns, RANKINGS_RECEIVED_CNT);
1568 // expand name column to fill the entire row
1569 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1570 namesize += available_space;
1571 columnsize.x += available_space;
1573 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1574 panel_size.y += panel_bg_padding * 2;
1578 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1579 if(panel.current_panel_bg != "0")
1580 end_pos.y += panel_bg_border * 2;
1582 if(panel_bg_padding)
1584 panel_pos += '1 1 0' * panel_bg_padding;
1585 panel_size -= '2 2 0' * panel_bg_padding;
1591 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1593 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1595 int column = 0, j = 0;
1596 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1597 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1604 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1605 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1606 else if(!((j + column) & 1) && sbt_highlight)
1607 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1609 str = count_ordinal(i+1);
1610 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1611 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1612 str = ColorTranslateRGB(grecordholder[i]);
1614 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1615 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1617 pos.y += 1.25 * hud_fontsize.y;
1619 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1623 pos.x += panel_size.x / columns;
1624 pos.y = panel_pos.y;
1627 strfree(zoned_name_self);
1629 panel_size.x += panel_bg_padding * 2; // restore initial width
1633 float scoreboard_time;
1634 bool have_weapon_stats;
1635 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1637 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1639 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1642 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1643 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1649 if (!have_weapon_stats)
1651 FOREACH(Weapons, it != WEP_Null, {
1652 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1653 if (weapon_stats >= 0)
1655 have_weapon_stats = true;
1659 if (!have_weapon_stats)
1666 bool have_item_stats;
1667 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1669 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1671 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1674 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1675 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1681 if (!have_item_stats)
1683 IL_EACH(default_order_items, true, {
1684 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1686 int q = g_inventory.inv_items[it.m_id];
1687 //q = 1; // debug: display all items
1690 have_item_stats = true;
1695 if (!have_item_stats)
1702 void Scoreboard_Draw()
1704 if(!autocvar__hud_configure)
1706 if(!hud_draw_maximized) return;
1708 // frametime checks allow to toggle the scoreboard even when the game is paused
1709 if(scoreboard_active) {
1710 if (scoreboard_fade_alpha == 0)
1711 scoreboard_time = time;
1712 if(hud_configure_menu_open == 1)
1713 scoreboard_fade_alpha = 1;
1714 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1715 if (scoreboard_fadeinspeed && frametime)
1716 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1718 scoreboard_fade_alpha = 1;
1719 if(hud_fontsize_str != autocvar_hud_fontsize)
1721 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1722 Scoreboard_initFieldSizes();
1723 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1727 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1728 if (scoreboard_fadeoutspeed && frametime)
1729 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1731 scoreboard_fade_alpha = 0;
1734 if (!scoreboard_fade_alpha)
1736 scoreboard_acc_fade_alpha = 0;
1737 scoreboard_itemstats_fade_alpha = 0;
1742 scoreboard_fade_alpha = 0;
1744 if (autocvar_hud_panel_scoreboard_dynamichud)
1747 HUD_Scale_Disable();
1749 if(scoreboard_fade_alpha <= 0)
1751 panel_fade_alpha *= scoreboard_fade_alpha;
1752 HUD_Panel_LoadCvars();
1754 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1755 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1756 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1757 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1758 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1759 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1760 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1762 // don't overlap with con_notify
1763 if(!autocvar__hud_configure)
1764 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1766 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1767 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1768 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1769 panel_size.x = fixed_scoreboard_width;
1771 Scoreboard_UpdatePlayerTeams();
1773 float initial_pos_y = panel_pos.y;
1774 vector pos = panel_pos;
1779 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1781 // Begin of Game Info Section
1782 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1783 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1785 // Game Info: Game Type
1786 str = MapInfo_Type_ToText(gametype);
1787 draw_beginBoldFont();
1788 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);
1791 // Game Info: Game Detail
1792 float tl = STAT(TIMELIMIT);
1793 float fl = STAT(FRAGLIMIT);
1794 float ll = STAT(LEADLIMIT);
1795 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1798 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1799 if(!gametype.m_hidelimits)
1804 str = strcat(str, "^7 / "); // delimiter
1807 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1808 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1809 (teamscores_label(ts_primary) == "fastest") ? "" :
1810 TranslateScoresLabel(teamscores_label(ts_primary))));
1814 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1815 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1816 (scores_label(ps_primary) == "fastest") ? "" :
1817 TranslateScoresLabel(scores_label(ps_primary))));
1822 if(tl > 0 || fl > 0)
1825 if (ll_and_fl && fl > 0)
1826 str = strcat(str, "^7 & ");
1828 str = strcat(str, "^7 / ");
1833 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1834 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1835 (teamscores_label(ts_primary) == "fastest") ? "" :
1836 TranslateScoresLabel(teamscores_label(ts_primary))));
1840 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1841 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1842 (scores_label(ps_primary) == "fastest") ? "" :
1843 TranslateScoresLabel(scores_label(ps_primary))));
1848 pos.y += sb_gameinfo_type_fontsize.y;
1849 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
1851 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1852 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1853 // End of Game Info Section
1855 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1856 if(panel.current_panel_bg != "0")
1857 pos.y += panel_bg_border;
1859 // Draw the scoreboard
1860 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1863 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1867 vector panel_bg_color_save = panel_bg_color;
1868 vector team_score_baseoffset;
1869 vector team_size_baseoffset;
1870 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1872 // put team score to the left of scoreboard (and team size to the right)
1873 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1874 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1875 if(panel.current_panel_bg != "0")
1877 team_score_baseoffset.x -= panel_bg_border;
1878 team_size_baseoffset.x += panel_bg_border;
1883 // put team score to the right of scoreboard (and team size to the left)
1884 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1885 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1886 if(panel.current_panel_bg != "0")
1888 team_score_baseoffset.x += panel_bg_border;
1889 team_size_baseoffset.x -= panel_bg_border;
1893 int team_size_total = 0;
1894 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1896 // calculate team size total (sum of all team sizes)
1897 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1898 if(tm.team != NUM_SPECTATOR)
1899 team_size_total += tm.team_size;
1902 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1904 if(tm.team == NUM_SPECTATOR)
1909 draw_beginBoldFont();
1910 vector rgb = Team_ColorRGB(tm.team);
1911 str = ftos(tm.(teamscores(ts_primary)));
1912 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1914 // team score on the left (default)
1915 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1919 // team score on the right
1920 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1922 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1924 // team size (if set to show on the side)
1925 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1927 // calculate the starting position for the whole team size info string
1928 str = sprintf("%d/%d", tm.team_size, team_size_total);
1929 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1931 // team size on the left
1932 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1936 // team size on the right
1937 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1939 str = sprintf("%d", tm.team_size);
1940 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1941 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1942 str = sprintf("/%d", team_size_total);
1943 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1947 // secondary score, e.g. keyhunt
1948 if(ts_primary != ts_secondary)
1950 str = ftos(tm.(teamscores(ts_secondary)));
1951 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1954 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1959 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1962 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1965 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1966 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1967 else if(panel_bg_color_team > 0)
1968 panel_bg_color = rgb * panel_bg_color_team;
1970 panel_bg_color = rgb;
1971 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1973 panel_bg_color = panel_bg_color_save;
1977 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1978 if(tm.team != NUM_SPECTATOR)
1981 // display it anyway
1982 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1985 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1986 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1987 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1988 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1990 if(MUTATOR_CALLHOOK(ShowRankings)) {
1991 string ranktitle = M_ARGV(0, string);
1992 if(race_speedaward) {
1993 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);
1994 pos.y += 1.25 * hud_fontsize.y;
1996 if(race_speedaward_alltimebest) {
1997 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);
1998 pos.y += 1.25 * hud_fontsize.y;
2000 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2003 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2006 for(pl = players.sort_next; pl; pl = pl.sort_next)
2008 if(pl.team == NUM_SPECTATOR)
2010 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2011 if(tm.team == NUM_SPECTATOR)
2013 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2014 draw_beginBoldFont();
2015 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2017 pos.y += 1.25 * hud_fontsize.y;
2019 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2020 pos.y += 1.25 * hud_fontsize.y;
2027 // print information about respawn status
2028 float respawn_time = STAT(RESPAWN_TIME);
2032 if(respawn_time < 0)
2034 // a negative number means we are awaiting respawn, time value is still the same
2035 respawn_time *= -1; // remove mark now that we checked it
2037 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2038 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2040 str = sprintf(_("^1Respawning in ^3%s^1..."),
2041 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2042 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2044 count_seconds(ceil(respawn_time - time))
2048 else if(time < respawn_time)
2050 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2051 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2052 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2054 count_seconds(ceil(respawn_time - time))
2058 else if(time >= respawn_time)
2059 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2061 pos.y += 1.2 * hud_fontsize.y;
2062 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2065 pos.y += 2 * hud_fontsize.y;
2066 if (scoreboard_fade_alpha < 1)
2067 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2068 else if (pos.y != scoreboard_bottom)
2070 if (pos.y > scoreboard_bottom)
2071 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2073 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));