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");
37 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
40 const int MAX_SBT_FIELDS = MAX_SCORE;
42 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
43 float sbt_field_size[MAX_SBT_FIELDS + 1];
44 string sbt_field_title[MAX_SBT_FIELDS + 1];
47 string autocvar_hud_fontsize;
48 string hud_fontsize_str;
53 float sbt_fg_alpha_self;
55 float sbt_highlight_alpha;
56 float sbt_highlight_alpha_self;
57 float sbt_highlight_alpha_eliminated;
59 // provide basic panel cvars to old clients
60 // TODO remove them after a future release (0.8.2+)
61 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
62 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
63 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
64 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
65 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
66 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
67 noref string autocvar_hud_panel_scoreboard_bg_border = "";
68 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
70 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
71 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
72 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
73 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
74 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
77 bool autocvar_hud_panel_scoreboard_table_highlight = true;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
81 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
82 float autocvar_hud_panel_scoreboard_namesize = 15;
83 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 float autocvar_hud_panel_scoreboard_spectators_position = 1;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_itemstats = true;
93 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
94 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
95 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
96 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
99 bool autocvar_hud_panel_scoreboard_dynamichud = false;
101 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
102 bool autocvar_hud_panel_scoreboard_others_showscore = true;
103 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
104 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
105 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
106 bool autocvar_hud_panel_scoreboard_playerid = false;
107 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
108 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
110 // mode 0: returns translated label
111 // mode 1: prints name and description of all the labels
112 string Label_getInfo(string label, int mode)
115 label = "bckills"; // first case in the switch
119 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
120 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
121 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")));
122 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
123 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
124 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
125 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
126 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
127 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
128 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
129 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
130 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
131 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
132 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
133 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
134 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
135 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
136 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
137 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
138 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
139 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
140 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
141 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
142 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
143 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
144 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
145 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
146 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")));
147 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
148 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
149 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
150 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
151 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
152 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
153 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
154 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
155 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
156 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
157 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
158 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
159 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
160 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
161 default: return label;
166 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
167 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
169 #define SB_EXTRA_SORTING_FIELDS 5
170 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
171 void Scoreboard_InitScores()
175 ps_primary = ps_secondary = NULL;
176 ts_primary = ts_secondary = -1;
177 FOREACH(Scores, true, {
178 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
179 if(f == SFL_SORT_PRIO_PRIMARY)
181 if(f == SFL_SORT_PRIO_SECONDARY)
183 if(ps_primary == it || ps_secondary == it)
185 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
186 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
187 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
188 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
189 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
191 if(ps_secondary == NULL)
192 ps_secondary = ps_primary;
194 for(i = 0; i < MAX_TEAMSCORE; ++i)
196 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
197 if(f == SFL_SORT_PRIO_PRIMARY)
199 if(f == SFL_SORT_PRIO_SECONDARY)
202 if(ts_secondary == -1)
203 ts_secondary = ts_primary;
205 Cmd_Scoreboard_SetFields(0);
209 void Scoreboard_UpdatePlayerTeams()
213 for(pl = players.sort_next; pl; pl = pl.sort_next)
216 int Team = entcs_GetScoreTeam(pl.sv_entnum);
217 if(SetTeam(pl, Team))
220 Scoreboard_UpdatePlayerPos(pl);
224 pl = players.sort_next;
229 print(strcat("PNUM: ", ftos(num), "\n"));
234 int Scoreboard_CompareScore(int vl, int vr, int f)
236 TC(int, vl); TC(int, vr); TC(int, f);
237 if(f & SFL_ZERO_IS_WORST)
239 if(vl == 0 && vr != 0)
241 if(vl != 0 && vr == 0)
245 return IS_INCREASING(f);
247 return IS_DECREASING(f);
251 float Scoreboard_ComparePlayerScores(entity left, entity right)
253 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
254 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
261 if(vl == NUM_SPECTATOR)
263 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
265 if(!left.gotscores && right.gotscores)
272 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
276 if (!fld) fld = ps_primary;
277 else if (ps_secondary == ps_primary) continue;
278 else fld = ps_secondary;
282 fld = sb_extra_sorting_field[i];
283 if (fld == ps_primary || fld == ps_secondary) continue;
287 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
288 if (r >= 0) return r;
291 if (left.sv_entnum < right.sv_entnum)
297 void Scoreboard_UpdatePlayerPos(entity player)
300 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
302 SORT_SWAP(player, ent);
304 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
306 SORT_SWAP(ent, player);
310 float Scoreboard_CompareTeamScores(entity left, entity right)
312 if(left.team == NUM_SPECTATOR)
314 if(right.team == NUM_SPECTATOR)
319 for(int i = -2; i < MAX_TEAMSCORE; ++i)
323 if (fld_idx == -1) fld_idx = ts_primary;
324 else if (ts_secondary == ts_primary) continue;
325 else fld_idx = ts_secondary;
330 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
333 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
334 if (r >= 0) return r;
337 if (left.team < right.team)
343 void Scoreboard_UpdateTeamPos(entity Team)
346 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
348 SORT_SWAP(Team, ent);
350 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
352 SORT_SWAP(ent, Team);
356 void Cmd_Scoreboard_Help()
358 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
359 LOG_HELP(_("Usage:"));
360 LOG_HELP("^2scoreboard_columns_set ^3default");
361 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
362 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
363 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
364 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
365 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
366 LOG_HELP(_("The following field names are recognized (case insensitive):"));
372 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
373 "of game types, then a slash, to make the field show up only in these\n"
374 "or in all but these game types. You can also specify 'all' as a\n"
375 "field to show all fields available for the current game mode."));
378 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
379 "include/exclude ALL teams/noteams game modes."));
382 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
383 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
384 "right of the vertical bar aligned to the right."));
385 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
386 "other gamemodes except DM."));
389 // NOTE: adding a gametype with ? to not warn for an optional field
390 // make sure it's excluded in a previous exclusive rule, if any
391 // otherwise the previous exclusive rule warns anyway
392 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
393 #define SCOREBOARD_DEFAULT_COLUMNS \
394 "ping pl fps name |" \
395 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
396 " -teams,lms/deaths +ft,tdm/deaths" \
398 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
399 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
400 " +tdm,ft,dom,ons,as/teamkills"\
401 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
402 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
403 " +lms/lives +lms/rank" \
404 " +kh/kckills +kh/losses +kh/caps" \
405 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
406 " +as/objectives +nb/faults +nb/goals" \
407 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
408 " +dom/ticks +dom/takes" \
409 " -lms,rc,cts,inv,nb/score"
411 void Cmd_Scoreboard_SetFields(int argc)
416 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
420 return; // do nothing, we don't know gametype and scores yet
422 // sbt_fields uses strunzone on the titles!
423 if(!sbt_field_title[0])
424 for(i = 0; i < MAX_SBT_FIELDS; ++i)
425 sbt_field_title[i] = strzone("(null)");
427 // TODO: re enable with gametype dependant cvars?
428 if(argc < 3) // no arguments provided
429 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
432 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
436 if(argv(2) == "default" || argv(2) == "expand_default")
438 if(argv(2) == "expand_default")
439 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
440 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
442 else if(argv(2) == "all" || argv(2) == "ALL")
444 string s = "ping pl name |"; // scores without label (not really scores)
447 // scores without label
448 s = strcat(s, " ", "sum");
449 s = strcat(s, " ", "kdratio");
450 s = strcat(s, " ", "frags");
452 FOREACH(Scores, true, {
454 if(it != ps_secondary)
455 if(scores_label(it) != "")
456 s = strcat(s, " ", scores_label(it));
458 if(ps_secondary != ps_primary)
459 s = strcat(s, " ", scores_label(ps_secondary));
460 s = strcat(s, " ", scores_label(ps_primary));
461 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
468 hud_fontsize = HUD_GetFontsize("hud_fontsize");
470 for(i = 1; i < argc - 1; ++i)
473 bool nocomplain = false;
474 if(substring(str, 0, 1) == "?")
477 str = substring(str, 1, strlen(str) - 1);
480 slash = strstrofs(str, "/", 0);
483 pattern = substring(str, 0, slash);
484 str = substring(str, slash + 1, strlen(str) - (slash + 1));
486 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
490 str = strtolower(str);
491 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
492 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
497 // fields without a label (not networked via the score system)
498 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
499 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
500 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
501 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
502 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
503 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
504 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
505 default: // fields with a label
507 // map alternative labels
508 if (str == "damage") str = "dmg";
509 if (str == "damagetaken") str = "dmgtaken";
511 FOREACH(Scores, true, {
512 if (str == strtolower(scores_label(it))) {
514 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
518 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
519 if(!nocomplain && str != "fps") // server can disable the fps field
520 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
522 strfree(sbt_field_title[sbt_num_fields]);
523 sbt_field_size[sbt_num_fields] = 0;
527 sbt_field[sbt_num_fields] = j;
530 if(j == ps_secondary)
531 have_secondary = true;
536 if(sbt_num_fields >= MAX_SBT_FIELDS)
540 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
542 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
543 have_secondary = true;
544 if(ps_primary == ps_secondary)
545 have_secondary = true;
546 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
548 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
552 strfree(sbt_field_title[sbt_num_fields]);
553 for(i = sbt_num_fields; i > 0; --i)
555 sbt_field_title[i] = sbt_field_title[i-1];
556 sbt_field_size[i] = sbt_field_size[i-1];
557 sbt_field[i] = sbt_field[i-1];
559 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
560 sbt_field[0] = SP_NAME;
562 LOG_INFO("fixed missing field 'name'");
566 strfree(sbt_field_title[sbt_num_fields]);
567 for(i = sbt_num_fields; i > 1; --i)
569 sbt_field_title[i] = sbt_field_title[i-1];
570 sbt_field_size[i] = sbt_field_size[i-1];
571 sbt_field[i] = sbt_field[i-1];
573 sbt_field_title[1] = strzone("|");
574 sbt_field[1] = SP_SEPARATOR;
575 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
577 LOG_INFO("fixed missing field '|'");
580 else if(!have_separator)
582 strcpy(sbt_field_title[sbt_num_fields], "|");
583 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
584 sbt_field[sbt_num_fields] = SP_SEPARATOR;
586 LOG_INFO("fixed missing field '|'");
590 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
591 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
592 sbt_field[sbt_num_fields] = ps_secondary;
594 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
598 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
599 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
600 sbt_field[sbt_num_fields] = ps_primary;
602 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
606 sbt_field[sbt_num_fields] = SP_END;
609 string Scoreboard_AddPlayerId(string pl_name, entity pl)
611 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
612 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
613 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
617 vector sbt_field_rgb;
618 string sbt_field_icon0;
619 string sbt_field_icon1;
620 string sbt_field_icon2;
621 vector sbt_field_icon0_rgb;
622 vector sbt_field_icon1_rgb;
623 vector sbt_field_icon2_rgb;
624 string Scoreboard_GetName(entity pl)
626 if(ready_waiting && pl.ready)
628 sbt_field_icon0 = "gfx/scoreboard/player_ready";
632 int f = entcs_GetClientColors(pl.sv_entnum);
634 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
635 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
636 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
637 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
638 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
641 return entcs_GetName(pl.sv_entnum);
644 string Scoreboard_GetField(entity pl, PlayerScoreField field)
646 float tmp, num, denom;
649 sbt_field_rgb = '1 1 1';
650 sbt_field_icon0 = "";
651 sbt_field_icon1 = "";
652 sbt_field_icon2 = "";
653 sbt_field_icon0_rgb = '1 1 1';
654 sbt_field_icon1_rgb = '1 1 1';
655 sbt_field_icon2_rgb = '1 1 1';
660 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
661 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
665 tmp = max(0, min(220, f-80)) / 220;
666 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
672 f = pl.ping_packetloss;
673 tmp = pl.ping_movementloss;
674 if(f == 0 && tmp == 0)
676 str = ftos(ceil(f * 100));
678 str = strcat(str, "~", ftos(ceil(tmp * 100)));
679 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
680 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
684 str = Scoreboard_GetName(pl);
685 if (autocvar_hud_panel_scoreboard_playerid)
686 str = Scoreboard_AddPlayerId(str, pl);
690 f = pl.(scores(SP_KILLS));
691 f -= pl.(scores(SP_SUICIDES));
695 num = pl.(scores(SP_KILLS));
696 denom = pl.(scores(SP_DEATHS));
699 sbt_field_rgb = '0 1 0';
700 str = sprintf("%d", num);
701 } else if(num <= 0) {
702 sbt_field_rgb = '1 0 0';
703 str = sprintf("%.1f", num/denom);
705 str = sprintf("%.1f", num/denom);
709 f = pl.(scores(SP_KILLS));
710 f -= pl.(scores(SP_DEATHS));
713 sbt_field_rgb = '0 1 0';
715 sbt_field_rgb = '1 1 1';
717 sbt_field_rgb = '1 0 0';
723 float elo = pl.(scores(SP_ELO));
725 case -1: return "...";
726 case -2: return _("N/A");
727 default: return ftos(elo);
733 float fps = pl.(scores(SP_FPS));
736 sbt_field_rgb = '1 1 1';
737 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
739 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
740 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
744 case SP_DMG: case SP_DMGTAKEN:
745 return sprintf("%.1f k", pl.(scores(field)) / 1000);
747 default: case SP_SCORE:
748 tmp = pl.(scores(field));
749 f = scores_flags(field);
750 if(field == ps_primary)
751 sbt_field_rgb = '1 1 0';
752 else if(field == ps_secondary)
753 sbt_field_rgb = '0 1 1';
755 sbt_field_rgb = '1 1 1';
756 return ScoreString(f, tmp);
761 float sbt_fixcolumnwidth_len;
762 float sbt_fixcolumnwidth_iconlen;
763 float sbt_fixcolumnwidth_marginlen;
765 string Scoreboard_FixColumnWidth(int i, string str)
771 sbt_fixcolumnwidth_iconlen = 0;
773 if(sbt_field_icon0 != "")
775 sz = draw_getimagesize(sbt_field_icon0);
777 if(sbt_fixcolumnwidth_iconlen < f)
778 sbt_fixcolumnwidth_iconlen = f;
781 if(sbt_field_icon1 != "")
783 sz = draw_getimagesize(sbt_field_icon1);
785 if(sbt_fixcolumnwidth_iconlen < f)
786 sbt_fixcolumnwidth_iconlen = f;
789 if(sbt_field_icon2 != "")
791 sz = draw_getimagesize(sbt_field_icon2);
793 if(sbt_fixcolumnwidth_iconlen < f)
794 sbt_fixcolumnwidth_iconlen = f;
797 if(sbt_fixcolumnwidth_iconlen != 0)
799 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
800 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
803 sbt_fixcolumnwidth_marginlen = 0;
805 if(sbt_field[i] == SP_NAME) // name gets all remaining space
808 float remaining_space = 0;
809 for(j = 0; j < sbt_num_fields; ++j)
811 if (sbt_field[i] != SP_SEPARATOR)
812 remaining_space += sbt_field_size[j] + hud_fontsize.x;
813 sbt_field_size[i] = panel_size.x - remaining_space;
815 if (sbt_fixcolumnwidth_iconlen != 0)
816 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
817 float namesize = panel_size.x - remaining_space;
818 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
819 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
821 max_namesize = vid_conwidth - remaining_space;
824 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
826 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
827 if(sbt_field_size[i] < f)
828 sbt_field_size[i] = f;
833 void Scoreboard_initFieldSizes()
835 for(int i = 0; i < sbt_num_fields; ++i)
837 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
838 Scoreboard_FixColumnWidth(i, "");
842 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
845 vector column_dim = eY * panel_size.y;
847 column_dim.y -= 1.25 * hud_fontsize.y;
848 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
849 pos.x += hud_fontsize.x * 0.5;
850 for(i = 0; i < sbt_num_fields; ++i)
852 if(sbt_field[i] == SP_SEPARATOR)
854 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
857 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
858 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
859 pos.x += column_dim.x;
861 if(sbt_field[i] == SP_SEPARATOR)
863 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
864 for(i = sbt_num_fields - 1; i > 0; --i)
866 if(sbt_field[i] == SP_SEPARATOR)
869 pos.x -= sbt_field_size[i];
874 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
875 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
878 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
879 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
880 pos.x -= hud_fontsize.x;
885 pos.y += 1.25 * hud_fontsize.y;
889 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
891 TC(bool, is_self); TC(int, pl_number);
893 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
895 vector h_pos = item_pos;
896 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
897 // alternated rows highlighting
899 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
900 else if((sbt_highlight) && (!(pl_number % 2)))
901 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
903 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
905 vector pos = item_pos;
906 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
908 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
910 pos.x += hud_fontsize.x * 0.5;
911 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
912 vector tmp = '0 0 0';
914 PlayerScoreField field;
915 for(i = 0; i < sbt_num_fields; ++i)
917 field = sbt_field[i];
918 if(field == SP_SEPARATOR)
921 if(is_spec && field != SP_NAME && field != SP_PING) {
922 pos.x += sbt_field_size[i] + hud_fontsize.x;
925 str = Scoreboard_GetField(pl, field);
926 str = Scoreboard_FixColumnWidth(i, str);
928 pos.x += sbt_field_size[i] + hud_fontsize.x;
930 if(field == SP_NAME) {
931 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
932 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
934 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
935 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
938 tmp.x = sbt_field_size[i] + hud_fontsize.x;
939 if(sbt_field_icon0 != "")
940 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
941 if(sbt_field_icon1 != "")
942 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
943 if(sbt_field_icon2 != "")
944 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 if(sbt_field[i] == SP_SEPARATOR)
949 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
950 for(i = sbt_num_fields-1; i > 0; --i)
952 field = sbt_field[i];
953 if(field == SP_SEPARATOR)
956 if(is_spec && field != SP_NAME && field != SP_PING) {
957 pos.x -= sbt_field_size[i] + hud_fontsize.x;
961 str = Scoreboard_GetField(pl, field);
962 str = Scoreboard_FixColumnWidth(i, str);
964 if(field == SP_NAME) {
965 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
966 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
968 tmp.x = sbt_fixcolumnwidth_len;
969 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
972 tmp.x = sbt_field_size[i];
973 if(sbt_field_icon0 != "")
974 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
975 if(sbt_field_icon1 != "")
976 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
977 if(sbt_field_icon2 != "")
978 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
979 pos.x -= sbt_field_size[i] + hud_fontsize.x;
984 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
987 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
990 vector h_pos = item_pos;
991 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
993 bool complete = (this_team == NUM_SPECTATOR);
996 if((sbt_highlight) && (!(pl_number % 2)))
997 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
999 vector pos = item_pos;
1000 pos.x += hud_fontsize.x * 0.5;
1001 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1003 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1005 width_limit -= stringwidth("...", false, hud_fontsize);
1006 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1007 static float max_name_width = 0;
1009 float fieldsize = 0;
1010 float min_fieldsize = 0;
1011 float fieldpadding = hud_fontsize.x * 0.25;
1012 if(this_team == NUM_SPECTATOR)
1014 if(autocvar_hud_panel_scoreboard_spectators_showping)
1015 min_fieldsize = stringwidth("999", false, hud_fontsize);
1017 else if(autocvar_hud_panel_scoreboard_others_showscore)
1018 min_fieldsize = stringwidth("99", false, hud_fontsize);
1019 for(i = 0; pl; pl = pl.sort_next)
1021 if(pl.team != this_team)
1023 if(pl == ignored_pl)
1027 if(this_team == NUM_SPECTATOR)
1029 if(autocvar_hud_panel_scoreboard_spectators_showping)
1030 field = Scoreboard_GetField(pl, SP_PING);
1032 else if(autocvar_hud_panel_scoreboard_others_showscore)
1033 field = Scoreboard_GetField(pl, SP_SCORE);
1035 string str = entcs_GetName(pl.sv_entnum);
1036 if (autocvar_hud_panel_scoreboard_playerid)
1037 str = Scoreboard_AddPlayerId(str, pl);
1038 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1039 float column_width = stringwidth(str, true, hud_fontsize);
1040 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1042 if(column_width > max_name_width)
1043 max_name_width = column_width;
1044 column_width = max_name_width;
1048 fieldsize = stringwidth(field, false, hud_fontsize);
1049 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1052 if(pos.x + column_width > width_limit)
1057 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1062 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1063 pos.y += hud_fontsize.y * 1.25;
1067 vector name_pos = pos;
1068 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1069 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1070 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1073 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1074 h_size.y = hud_fontsize.y;
1075 vector field_pos = pos;
1076 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1077 field_pos.x += column_width - h_size.x;
1079 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1080 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1081 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1085 h_size.x = column_width + hud_fontsize.x * 0.25;
1086 h_size.y = hud_fontsize.y;
1087 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1089 pos.x += column_width;
1090 pos.x += hud_fontsize.x;
1092 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1095 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1097 int max_players = 999;
1098 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1100 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1103 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1104 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1105 height /= team_count;
1108 height -= panel_bg_padding * 2; // - padding
1109 max_players = floor(height / (hud_fontsize.y * 1.25));
1110 if(max_players <= 1)
1112 if(max_players == tm.team_size)
1117 entity me = playerslots[current_player];
1119 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1120 panel_size.y += panel_bg_padding * 2;
1123 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1124 if(panel.current_panel_bg != "0")
1125 end_pos.y += panel_bg_border * 2;
1127 if(panel_bg_padding)
1129 panel_pos += '1 1 0' * panel_bg_padding;
1130 panel_size -= '2 2 0' * panel_bg_padding;
1134 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1138 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1140 pos.y += 1.25 * hud_fontsize.y;
1143 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1145 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1148 // print header row and highlight columns
1149 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1151 // fill the table and draw the rows
1152 bool is_self = false;
1153 bool self_shown = false;
1155 for(pl = players.sort_next; pl; pl = pl.sort_next)
1157 if(pl.team != tm.team)
1159 if(i == max_players - 2 && pl != me)
1161 if(!self_shown && me.team == tm.team)
1163 Scoreboard_DrawItem(pos, rgb, me, true, i);
1165 pos.y += 1.25 * hud_fontsize.y;
1169 if(i >= max_players - 1)
1171 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1174 is_self = (pl.sv_entnum == current_player);
1175 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1178 pos.y += 1.25 * hud_fontsize.y;
1182 panel_size.x += panel_bg_padding * 2; // restore initial width
1186 bool Scoreboard_WouldDraw()
1188 if (MUTATOR_CALLHOOK(DrawScoreboard))
1190 else if (QuickMenu_IsOpened())
1192 else if (HUD_Radar_Clickable())
1194 else if (scoreboard_showscores)
1196 else if (intermission == 1)
1198 else if (intermission == 2)
1200 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1201 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1205 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1210 float average_accuracy;
1211 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1213 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1215 WepSet weapons_stat = WepSet_GetFromStat();
1216 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1217 int disownedcnt = 0;
1219 FOREACH(Weapons, it != WEP_Null, {
1220 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1222 WepSet set = it.m_wepset;
1223 if(it.spawnflags & WEP_TYPE_OTHER)
1228 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1230 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1237 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1238 if (weapon_cnt <= 0) return pos;
1241 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1243 int columnns = ceil(weapon_cnt / rows);
1245 float weapon_height = 29;
1246 float height = hud_fontsize.y + weapon_height;
1248 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);
1249 pos.y += 1.25 * hud_fontsize.y;
1250 if(panel.current_panel_bg != "0")
1251 pos.y += panel_bg_border;
1254 panel_size.y = height * rows;
1255 panel_size.y += panel_bg_padding * 2;
1257 float panel_bg_alpha_save = panel_bg_alpha;
1258 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1260 panel_bg_alpha = panel_bg_alpha_save;
1262 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1263 if(panel.current_panel_bg != "0")
1264 end_pos.y += panel_bg_border * 2;
1266 if(panel_bg_padding)
1268 panel_pos += '1 1 0' * panel_bg_padding;
1269 panel_size -= '2 2 0' * panel_bg_padding;
1273 vector tmp = panel_size;
1275 float weapon_width = tmp.x / columnns / rows;
1278 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1282 // column highlighting
1283 for (int i = 0; i < columnns; ++i)
1285 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);
1288 for (int i = 0; i < rows; ++i)
1289 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1292 average_accuracy = 0;
1293 int weapons_with_stats = 0;
1295 pos.x += weapon_width / 2;
1297 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1300 Accuracy_LoadColors();
1302 float oldposx = pos.x;
1306 FOREACH(Weapons, it != WEP_Null, {
1307 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1309 WepSet set = it.m_wepset;
1310 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1312 if (it.spawnflags & WEP_TYPE_OTHER)
1316 if (weapon_stats >= 0)
1317 weapon_alpha = sbt_fg_alpha;
1319 weapon_alpha = 0.2 * sbt_fg_alpha;
1322 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1324 if (weapon_stats >= 0) {
1325 weapons_with_stats += 1;
1326 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1329 s = sprintf("%d%%", weapon_stats * 100);
1332 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1334 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1335 rgb = Accuracy_GetColor(weapon_stats);
1337 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1339 tmpos.x += weapon_width * rows;
1340 pos.x += weapon_width * rows;
1341 if (rows == 2 && column == columnns - 1) {
1349 if (weapons_with_stats)
1350 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1352 panel_size.x += panel_bg_padding * 2; // restore initial width
1357 bool is_item_filtered(entity it)
1359 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1361 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1364 if (it.instanceOfArmor || it.instanceOfHealth)
1366 int ha_mask = floor(mask) % 10;
1369 default: return false;
1370 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1371 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1372 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1373 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1376 if (it.instanceOfAmmo)
1378 int ammo_mask = floor(mask / 10) % 10;
1379 return (ammo_mask == 1);
1384 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1386 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1388 int disowned_cnt = 0;
1389 int uninteresting_cnt = 0;
1390 IL_EACH(default_order_items, true, {
1391 int q = g_inventory.inv_items[it.m_id];
1392 //q = 1; // debug: display all items
1393 if (is_item_filtered(it))
1394 ++uninteresting_cnt;
1398 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1399 int n = items_cnt - disowned_cnt;
1400 if (n <= 0) return pos;
1402 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1403 int columnns = max(6, ceil(n / rows));
1406 float fontsize = height * 1/3;
1407 float item_height = height * 2/3;
1409 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1410 pos.y += 1.25 * hud_fontsize.y;
1411 if(panel.current_panel_bg != "0")
1412 pos.y += panel_bg_border;
1415 panel_size.y = height * rows;
1416 panel_size.y += panel_bg_padding * 2;
1418 float panel_bg_alpha_save = panel_bg_alpha;
1419 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1421 panel_bg_alpha = panel_bg_alpha_save;
1423 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1424 if(panel.current_panel_bg != "0")
1425 end_pos.y += panel_bg_border * 2;
1427 if(panel_bg_padding)
1429 panel_pos += '1 1 0' * panel_bg_padding;
1430 panel_size -= '2 2 0' * panel_bg_padding;
1434 vector tmp = panel_size;
1436 float item_width = tmp.x / columnns / rows;
1439 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1443 // column highlighting
1444 for (int i = 0; i < columnns; ++i)
1446 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);
1449 for (int i = 0; i < rows; ++i)
1450 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);
1454 pos.x += item_width / 2;
1456 float oldposx = pos.x;
1460 IL_EACH(default_order_items, !is_item_filtered(it), {
1461 int n = g_inventory.inv_items[it.m_id];
1462 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1463 if (n <= 0) continue;
1464 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);
1466 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1467 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);
1468 tmpos.x += item_width * rows;
1469 pos.x += item_width * rows;
1470 if (rows == 2 && column == columnns - 1) {
1478 panel_size.x += panel_bg_padding * 2; // restore initial width
1483 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1485 pos.x += hud_fontsize.x * 0.25;
1486 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1487 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1488 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1490 pos.y += hud_fontsize.y;
1495 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1496 float stat_secrets_found, stat_secrets_total;
1497 float stat_monsters_killed, stat_monsters_total;
1501 // get monster stats
1502 stat_monsters_killed = STAT(MONSTERS_KILLED);
1503 stat_monsters_total = STAT(MONSTERS_TOTAL);
1505 // get secrets stats
1506 stat_secrets_found = STAT(SECRETS_FOUND);
1507 stat_secrets_total = STAT(SECRETS_TOTAL);
1509 // get number of rows
1510 if(stat_secrets_total)
1512 if(stat_monsters_total)
1515 // if no rows, return
1519 // draw table header
1520 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1521 pos.y += 1.25 * hud_fontsize.y;
1522 if(panel.current_panel_bg != "0")
1523 pos.y += panel_bg_border;
1526 panel_size.y = hud_fontsize.y * rows;
1527 panel_size.y += panel_bg_padding * 2;
1530 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1531 if(panel.current_panel_bg != "0")
1532 end_pos.y += panel_bg_border * 2;
1534 if(panel_bg_padding)
1536 panel_pos += '1 1 0' * panel_bg_padding;
1537 panel_size -= '2 2 0' * panel_bg_padding;
1541 vector tmp = panel_size;
1544 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1547 if(stat_monsters_total)
1549 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1550 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1554 if(stat_secrets_total)
1556 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1557 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1560 panel_size.x += panel_bg_padding * 2; // restore initial width
1565 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1568 RANKINGS_RECEIVED_CNT = 0;
1569 for (i=RANKINGS_CNT-1; i>=0; --i)
1571 ++RANKINGS_RECEIVED_CNT;
1573 if (RANKINGS_RECEIVED_CNT == 0)
1576 vector hl_rgb = rgb + '0.5 0.5 0.5';
1578 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1579 pos.y += 1.25 * hud_fontsize.y;
1580 if(panel.current_panel_bg != "0")
1581 pos.y += panel_bg_border;
1586 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1588 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1593 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1595 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1599 float ranksize = 3 * hud_fontsize.x;
1600 float timesize = 5 * hud_fontsize.x;
1601 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1602 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1603 columns = min(columns, RANKINGS_RECEIVED_CNT);
1604 int rows = ceil(RANKINGS_RECEIVED_CNT / columns);
1606 // expand name column to fill the entire row
1607 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1608 namesize += available_space;
1609 columnsize.x += available_space;
1611 panel_size.y = rows * 1.25 * hud_fontsize.y;
1612 panel_size.y += panel_bg_padding * 2;
1616 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1617 if(panel.current_panel_bg != "0")
1618 end_pos.y += panel_bg_border * 2;
1620 if(panel_bg_padding)
1622 panel_pos += '1 1 0' * panel_bg_padding;
1623 panel_size -= '2 2 0' * panel_bg_padding;
1629 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1631 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1633 int column = 0, j = 0;
1634 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1635 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1642 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1643 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1644 else if(!((j + column) & 1) && sbt_highlight)
1645 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1647 str = count_ordinal(i+1);
1648 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1649 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1650 str = ColorTranslateRGB(grecordholder[i]);
1652 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1653 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1655 pos.y += 1.25 * hud_fontsize.y;
1661 pos.x += panel_size.x / columns;
1662 pos.y = panel_pos.y;
1665 strfree(zoned_name_self);
1667 panel_size.x += panel_bg_padding * 2; // restore initial width
1671 float scoreboard_time;
1672 bool have_weapon_stats;
1673 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1675 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1677 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1680 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1681 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1687 if (!have_weapon_stats)
1689 FOREACH(Weapons, it != WEP_Null, {
1690 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1691 if (weapon_stats >= 0)
1693 have_weapon_stats = true;
1697 if (!have_weapon_stats)
1704 bool have_item_stats;
1705 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1707 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1709 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1712 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1713 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1719 if (!have_item_stats)
1721 IL_EACH(default_order_items, true, {
1722 if (!is_item_filtered(it))
1724 int q = g_inventory.inv_items[it.m_id];
1725 //q = 1; // debug: display all items
1728 have_item_stats = true;
1733 if (!have_item_stats)
1740 vector Scoreboard_Spectators_Draw(vector pos) {
1745 for(pl = players.sort_next; pl; pl = pl.sort_next)
1747 if(pl.team == NUM_SPECTATOR)
1749 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1750 if(tm.team == NUM_SPECTATOR)
1752 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1753 draw_beginBoldFont();
1754 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1756 pos.y += 1.25 * hud_fontsize.y;
1758 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1759 pos.y += 1.25 * hud_fontsize.y;
1764 if (str != "") // if there's at least one spectator
1765 pos.y += 0.5 * hud_fontsize.y;
1770 void Scoreboard_Draw()
1772 if(!autocvar__hud_configure)
1774 if(!hud_draw_maximized) return;
1776 // frametime checks allow to toggle the scoreboard even when the game is paused
1777 if(scoreboard_active) {
1778 if (scoreboard_fade_alpha == 0)
1779 scoreboard_time = time;
1780 if(hud_configure_menu_open == 1)
1781 scoreboard_fade_alpha = 1;
1782 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1783 if (scoreboard_fadeinspeed && frametime)
1784 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1786 scoreboard_fade_alpha = 1;
1787 if(hud_fontsize_str != autocvar_hud_fontsize)
1789 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1790 Scoreboard_initFieldSizes();
1791 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1795 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1796 if (scoreboard_fadeoutspeed && frametime)
1797 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1799 scoreboard_fade_alpha = 0;
1802 if (!scoreboard_fade_alpha)
1804 scoreboard_acc_fade_alpha = 0;
1805 scoreboard_itemstats_fade_alpha = 0;
1810 scoreboard_fade_alpha = 0;
1812 if (autocvar_hud_panel_scoreboard_dynamichud)
1815 HUD_Scale_Disable();
1817 if(scoreboard_fade_alpha <= 0)
1819 panel_fade_alpha *= scoreboard_fade_alpha;
1820 HUD_Panel_LoadCvars();
1822 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1823 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1824 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1825 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1826 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1827 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1828 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1830 // don't overlap with con_notify
1831 if(!autocvar__hud_configure)
1832 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1834 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1835 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1836 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1837 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1838 panel_pos.x = scoreboard_left;
1839 panel_size.x = fixed_scoreboard_width;
1841 Scoreboard_UpdatePlayerTeams();
1843 scoreboard_top = panel_pos.y;
1844 vector pos = panel_pos;
1849 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1851 // Begin of Game Info Section
1852 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1853 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1855 // Game Info: Game Type
1856 str = MapInfo_Type_ToText(gametype);
1857 draw_beginBoldFont();
1858 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);
1861 // Game Info: Game Detail
1862 float tl = STAT(TIMELIMIT);
1863 float fl = STAT(FRAGLIMIT);
1864 float ll = STAT(LEADLIMIT);
1865 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1868 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1869 if(!gametype.m_hidelimits)
1874 str = strcat(str, "^7 / "); // delimiter
1877 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1878 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1879 (teamscores_label(ts_primary) == "fastest") ? "" :
1880 TranslateScoresLabel(teamscores_label(ts_primary))));
1884 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1885 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1886 (scores_label(ps_primary) == "fastest") ? "" :
1887 TranslateScoresLabel(scores_label(ps_primary))));
1892 if(tl > 0 || fl > 0)
1895 if (ll_and_fl && fl > 0)
1896 str = strcat(str, "^7 & ");
1898 str = strcat(str, "^7 / ");
1903 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1904 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1905 (teamscores_label(ts_primary) == "fastest") ? "" :
1906 TranslateScoresLabel(teamscores_label(ts_primary))));
1910 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1911 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1912 (scores_label(ps_primary) == "fastest") ? "" :
1913 TranslateScoresLabel(scores_label(ps_primary))));
1918 pos.y += sb_gameinfo_type_fontsize.y;
1919 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
1921 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1922 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1923 // End of Game Info Section
1925 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1926 if(panel.current_panel_bg != "0")
1927 pos.y += panel_bg_border;
1929 // Draw the scoreboard
1930 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1933 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1937 vector panel_bg_color_save = panel_bg_color;
1938 vector team_score_baseoffset;
1939 vector team_size_baseoffset;
1940 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1942 // put team score to the left of scoreboard (and team size to the right)
1943 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1944 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1945 if(panel.current_panel_bg != "0")
1947 team_score_baseoffset.x -= panel_bg_border;
1948 team_size_baseoffset.x += panel_bg_border;
1953 // put team score to the right of scoreboard (and team size to the left)
1954 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1955 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1956 if(panel.current_panel_bg != "0")
1958 team_score_baseoffset.x += panel_bg_border;
1959 team_size_baseoffset.x -= panel_bg_border;
1963 int team_size_total = 0;
1964 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1966 // calculate team size total (sum of all team sizes)
1967 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1968 if(tm.team != NUM_SPECTATOR)
1969 team_size_total += tm.team_size;
1972 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1974 if(tm.team == NUM_SPECTATOR)
1979 draw_beginBoldFont();
1980 vector rgb = Team_ColorRGB(tm.team);
1981 str = ftos(tm.(teamscores(ts_primary)));
1982 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1984 // team score on the left (default)
1985 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1989 // team score on the right
1990 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1992 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1994 // team size (if set to show on the side)
1995 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1997 // calculate the starting position for the whole team size info string
1998 str = sprintf("%d/%d", tm.team_size, team_size_total);
1999 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2001 // team size on the left
2002 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2006 // team size on the right
2007 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2009 str = sprintf("%d", tm.team_size);
2010 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2011 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2012 str = sprintf("/%d", team_size_total);
2013 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2017 // secondary score, e.g. keyhunt
2018 if(ts_primary != ts_secondary)
2020 str = ftos(tm.(teamscores(ts_secondary)));
2021 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2024 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2029 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2032 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2035 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2036 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2037 else if(panel_bg_color_team > 0)
2038 panel_bg_color = rgb * panel_bg_color_team;
2040 panel_bg_color = rgb;
2041 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2043 panel_bg_color = panel_bg_color_save;
2047 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2048 if(tm.team != NUM_SPECTATOR)
2051 // display it anyway
2052 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2055 // draw scoreboard spectators before accuracy and item stats
2056 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2057 pos = Scoreboard_Spectators_Draw(pos);
2060 // draw accuracy and item stats
2061 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2062 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2063 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2064 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2066 // draw scoreboard spectators after accuracy and item stats and before rankings
2067 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2068 pos = Scoreboard_Spectators_Draw(pos);
2071 if(MUTATOR_CALLHOOK(ShowRankings)) {
2072 string ranktitle = M_ARGV(0, string);
2073 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2074 if(race_speedaward) {
2075 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2076 pos.y += 1.25 * hud_fontsize.y;
2078 if(race_speedaward_alltimebest) {
2079 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2080 pos.y += 1.25 * hud_fontsize.y;
2082 if (race_speedaward || race_speedaward_alltimebest)
2083 pos.y += 0.25 * hud_fontsize.y;
2084 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2087 // draw scoreboard spectators after rankings
2088 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2089 pos = Scoreboard_Spectators_Draw(pos);
2092 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2094 // draw scoreboard spectators after mapstats
2095 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2096 pos = Scoreboard_Spectators_Draw(pos);
2100 // print information about respawn status
2101 float respawn_time = STAT(RESPAWN_TIME);
2105 if(respawn_time < 0)
2107 // a negative number means we are awaiting respawn, time value is still the same
2108 respawn_time *= -1; // remove mark now that we checked it
2110 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2111 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2113 str = sprintf(_("^1Respawning in ^3%s^1..."),
2114 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2115 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2117 count_seconds(ceil(respawn_time - time))
2121 else if(time < respawn_time)
2123 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2124 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2125 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2127 count_seconds(ceil(respawn_time - time))
2131 else if(time >= respawn_time)
2132 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2134 pos.y += 1.2 * hud_fontsize.y;
2135 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2138 pos.y += hud_fontsize.y;
2139 if (scoreboard_fade_alpha < 1)
2140 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2141 else if (pos.y != scoreboard_bottom)
2143 if (pos.y > scoreboard_bottom)
2144 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2146 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));