1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
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()
211 static float update_time;
212 if (time <= update_time)
218 for(pl = players.sort_next; pl; pl = pl.sort_next)
221 int Team = entcs_GetScoreTeam(pl.sv_entnum);
222 if(SetTeam(pl, Team))
225 Scoreboard_UpdatePlayerPos(pl);
229 pl = players.sort_next;
234 print(strcat("PNUM: ", ftos(num), "\n"));
239 int Scoreboard_CompareScore(int vl, int vr, int f)
241 TC(int, vl); TC(int, vr); TC(int, f);
242 if(f & SFL_ZERO_IS_WORST)
244 if(vl == 0 && vr != 0)
246 if(vl != 0 && vr == 0)
250 return IS_INCREASING(f);
252 return IS_DECREASING(f);
256 float Scoreboard_ComparePlayerScores(entity left, entity right)
258 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
259 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
266 if(vl == NUM_SPECTATOR)
268 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
270 if(!left.gotscores && right.gotscores)
277 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
281 if (!fld) fld = ps_primary;
282 else if (ps_secondary == ps_primary) continue;
283 else fld = ps_secondary;
287 fld = sb_extra_sorting_field[i];
288 if (fld == ps_primary || fld == ps_secondary) continue;
292 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
293 if (r >= 0) return r;
296 if (left.sv_entnum < right.sv_entnum)
302 void Scoreboard_UpdatePlayerPos(entity player)
305 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
307 SORT_SWAP(player, ent);
309 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
311 SORT_SWAP(ent, player);
315 float Scoreboard_CompareTeamScores(entity left, entity right)
317 if(left.team == NUM_SPECTATOR)
319 if(right.team == NUM_SPECTATOR)
324 for(int i = -2; i < MAX_TEAMSCORE; ++i)
328 if (fld_idx == -1) fld_idx = ts_primary;
329 else if (ts_secondary == ts_primary) continue;
330 else fld_idx = ts_secondary;
335 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
338 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
339 if (r >= 0) return r;
342 if (left.team < right.team)
348 void Scoreboard_UpdateTeamPos(entity Team)
351 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
353 SORT_SWAP(Team, ent);
355 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
357 SORT_SWAP(ent, Team);
361 void Cmd_Scoreboard_Help()
363 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
364 LOG_HELP(_("Usage:"));
365 LOG_HELP("^2scoreboard_columns_set ^3default");
366 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
367 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
368 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
369 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
370 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
371 LOG_HELP(_("The following field names are recognized (case insensitive):"));
377 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
378 "of game types, then a slash, to make the field show up only in these\n"
379 "or in all but these game types. You can also specify 'all' as a\n"
380 "field to show all fields available for the current game mode."));
383 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
384 "include/exclude ALL teams/noteams game modes."));
387 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
388 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
389 "right of the vertical bar aligned to the right."));
390 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
391 "other gamemodes except DM."));
394 // NOTE: adding a gametype with ? to not warn for an optional field
395 // make sure it's excluded in a previous exclusive rule, if any
396 // otherwise the previous exclusive rule warns anyway
397 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
398 #define SCOREBOARD_DEFAULT_COLUMNS \
399 "ping pl fps name |" \
400 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
401 " -teams,lms/deaths +ft,tdm/deaths" \
403 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
404 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
405 " +tdm,ft,dom,ons,as/teamkills"\
406 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
407 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
408 " +lms/lives +lms/rank" \
409 " +kh/kckills +kh/losses +kh/caps" \
410 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
411 " +as/objectives +nb/faults +nb/goals" \
412 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
413 " +dom/ticks +dom/takes" \
414 " -lms,rc,cts,inv,nb/score"
416 void Cmd_Scoreboard_SetFields(int argc)
421 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
425 return; // do nothing, we don't know gametype and scores yet
427 // sbt_fields uses strunzone on the titles!
428 if(!sbt_field_title[0])
429 for(i = 0; i < MAX_SBT_FIELDS; ++i)
430 sbt_field_title[i] = strzone("(null)");
432 // TODO: re enable with gametype dependant cvars?
433 if(argc < 3) // no arguments provided
434 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
437 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
441 if(argv(2) == "default" || argv(2) == "expand_default")
443 if(argv(2) == "expand_default")
444 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
445 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
447 else if(argv(2) == "all" || argv(2) == "ALL")
449 string s = "ping pl name |"; // scores without label (not really scores)
452 // scores without label
453 s = strcat(s, " ", "sum");
454 s = strcat(s, " ", "kdratio");
455 s = strcat(s, " ", "frags");
457 FOREACH(Scores, true, {
459 if(it != ps_secondary)
460 if(scores_label(it) != "")
461 s = strcat(s, " ", scores_label(it));
463 if(ps_secondary != ps_primary)
464 s = strcat(s, " ", scores_label(ps_secondary));
465 s = strcat(s, " ", scores_label(ps_primary));
466 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
473 hud_fontsize = HUD_GetFontsize("hud_fontsize");
475 for(i = 1; i < argc - 1; ++i)
478 bool nocomplain = false;
479 if(substring(str, 0, 1) == "?")
482 str = substring(str, 1, strlen(str) - 1);
485 slash = strstrofs(str, "/", 0);
488 pattern = substring(str, 0, slash);
489 str = substring(str, slash + 1, strlen(str) - (slash + 1));
491 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
495 str = strtolower(str);
496 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
497 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
502 // fields without a label (not networked via the score system)
503 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
504 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
505 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
506 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
507 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
508 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
509 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
510 default: // fields with a label
512 // map alternative labels
513 if (str == "damage") str = "dmg";
514 if (str == "damagetaken") str = "dmgtaken";
516 FOREACH(Scores, true, {
517 if (str == strtolower(scores_label(it))) {
519 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
523 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
524 if(!nocomplain && str != "fps") // server can disable the fps field
525 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
527 strfree(sbt_field_title[sbt_num_fields]);
528 sbt_field_size[sbt_num_fields] = 0;
532 sbt_field[sbt_num_fields] = j;
535 if(j == ps_secondary)
536 have_secondary = true;
541 if(sbt_num_fields >= MAX_SBT_FIELDS)
545 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
547 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
548 have_secondary = true;
549 if(ps_primary == ps_secondary)
550 have_secondary = true;
551 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
553 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
557 strfree(sbt_field_title[sbt_num_fields]);
558 for(i = sbt_num_fields; i > 0; --i)
560 sbt_field_title[i] = sbt_field_title[i-1];
561 sbt_field_size[i] = sbt_field_size[i-1];
562 sbt_field[i] = sbt_field[i-1];
564 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
565 sbt_field[0] = SP_NAME;
567 LOG_INFO("fixed missing field 'name'");
571 strfree(sbt_field_title[sbt_num_fields]);
572 for(i = sbt_num_fields; i > 1; --i)
574 sbt_field_title[i] = sbt_field_title[i-1];
575 sbt_field_size[i] = sbt_field_size[i-1];
576 sbt_field[i] = sbt_field[i-1];
578 sbt_field_title[1] = strzone("|");
579 sbt_field[1] = SP_SEPARATOR;
580 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
582 LOG_INFO("fixed missing field '|'");
585 else if(!have_separator)
587 strcpy(sbt_field_title[sbt_num_fields], "|");
588 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
589 sbt_field[sbt_num_fields] = SP_SEPARATOR;
591 LOG_INFO("fixed missing field '|'");
595 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
596 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
597 sbt_field[sbt_num_fields] = ps_secondary;
599 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
603 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
604 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
605 sbt_field[sbt_num_fields] = ps_primary;
607 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
611 sbt_field[sbt_num_fields] = SP_END;
614 string Scoreboard_AddPlayerId(string pl_name, entity pl)
616 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
617 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
618 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
622 vector sbt_field_rgb;
623 string sbt_field_icon0;
624 string sbt_field_icon1;
625 string sbt_field_icon2;
626 vector sbt_field_icon0_rgb;
627 vector sbt_field_icon1_rgb;
628 vector sbt_field_icon2_rgb;
629 string Scoreboard_GetName(entity pl)
631 if(ready_waiting && pl.ready)
633 sbt_field_icon0 = "gfx/scoreboard/player_ready";
637 int f = entcs_GetClientColors(pl.sv_entnum);
639 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
640 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
641 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
642 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
643 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
646 return entcs_GetName(pl.sv_entnum);
649 string Scoreboard_GetField(entity pl, PlayerScoreField field)
651 float tmp, num, denom;
654 sbt_field_rgb = '1 1 1';
655 sbt_field_icon0 = "";
656 sbt_field_icon1 = "";
657 sbt_field_icon2 = "";
658 sbt_field_icon0_rgb = '1 1 1';
659 sbt_field_icon1_rgb = '1 1 1';
660 sbt_field_icon2_rgb = '1 1 1';
665 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
666 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
670 tmp = max(0, min(220, f-80)) / 220;
671 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
677 f = pl.ping_packetloss;
678 tmp = pl.ping_movementloss;
679 if(f == 0 && tmp == 0)
681 str = ftos(ceil(f * 100));
683 str = strcat(str, "~", ftos(ceil(tmp * 100)));
684 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
685 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
689 str = Scoreboard_GetName(pl);
690 if (autocvar_hud_panel_scoreboard_playerid)
691 str = Scoreboard_AddPlayerId(str, pl);
695 f = pl.(scores(SP_KILLS));
696 f -= pl.(scores(SP_SUICIDES));
700 num = pl.(scores(SP_KILLS));
701 denom = pl.(scores(SP_DEATHS));
704 sbt_field_rgb = '0 1 0';
705 str = sprintf("%d", num);
706 } else if(num <= 0) {
707 sbt_field_rgb = '1 0 0';
708 str = sprintf("%.1f", num/denom);
710 str = sprintf("%.1f", num/denom);
714 f = pl.(scores(SP_KILLS));
715 f -= pl.(scores(SP_DEATHS));
718 sbt_field_rgb = '0 1 0';
720 sbt_field_rgb = '1 1 1';
722 sbt_field_rgb = '1 0 0';
728 float elo = pl.(scores(SP_ELO));
730 case -1: return "...";
731 case -2: return _("N/A");
732 default: return ftos(elo);
738 float fps = pl.(scores(SP_FPS));
741 sbt_field_rgb = '1 1 1';
742 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
744 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
745 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
749 case SP_DMG: case SP_DMGTAKEN:
750 return sprintf("%.1f k", pl.(scores(field)) / 1000);
752 default: case SP_SCORE:
753 tmp = pl.(scores(field));
754 f = scores_flags(field);
755 if(field == ps_primary)
756 sbt_field_rgb = '1 1 0';
757 else if(field == ps_secondary)
758 sbt_field_rgb = '0 1 1';
760 sbt_field_rgb = '1 1 1';
761 return ScoreString(f, tmp);
766 float sbt_fixcolumnwidth_len;
767 float sbt_fixcolumnwidth_iconlen;
768 float sbt_fixcolumnwidth_marginlen;
770 string Scoreboard_FixColumnWidth(int i, string str)
776 sbt_fixcolumnwidth_iconlen = 0;
778 if(sbt_field_icon0 != "")
780 sz = draw_getimagesize(sbt_field_icon0);
782 if(sbt_fixcolumnwidth_iconlen < f)
783 sbt_fixcolumnwidth_iconlen = f;
786 if(sbt_field_icon1 != "")
788 sz = draw_getimagesize(sbt_field_icon1);
790 if(sbt_fixcolumnwidth_iconlen < f)
791 sbt_fixcolumnwidth_iconlen = f;
794 if(sbt_field_icon2 != "")
796 sz = draw_getimagesize(sbt_field_icon2);
798 if(sbt_fixcolumnwidth_iconlen < f)
799 sbt_fixcolumnwidth_iconlen = f;
802 if(sbt_fixcolumnwidth_iconlen != 0)
804 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
805 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
808 sbt_fixcolumnwidth_marginlen = 0;
810 if(sbt_field[i] == SP_NAME) // name gets all remaining space
813 float remaining_space = 0;
814 for(j = 0; j < sbt_num_fields; ++j)
816 if (sbt_field[i] != SP_SEPARATOR)
817 remaining_space += sbt_field_size[j] + hud_fontsize.x;
818 sbt_field_size[i] = panel_size.x - remaining_space;
820 if (sbt_fixcolumnwidth_iconlen != 0)
821 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
822 float namesize = panel_size.x - remaining_space;
823 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
824 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
826 max_namesize = vid_conwidth - remaining_space;
829 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
831 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
832 if(sbt_field_size[i] < f)
833 sbt_field_size[i] = f;
838 void Scoreboard_initFieldSizes()
840 for(int i = 0; i < sbt_num_fields; ++i)
842 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
843 Scoreboard_FixColumnWidth(i, "");
847 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
850 vector column_dim = eY * panel_size.y;
852 column_dim.y -= 1.25 * hud_fontsize.y;
853 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
854 pos.x += hud_fontsize.x * 0.5;
855 for(i = 0; i < sbt_num_fields; ++i)
857 if(sbt_field[i] == SP_SEPARATOR)
859 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
862 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
863 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
864 pos.x += column_dim.x;
866 if(sbt_field[i] == SP_SEPARATOR)
868 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
869 for(i = sbt_num_fields - 1; i > 0; --i)
871 if(sbt_field[i] == SP_SEPARATOR)
874 pos.x -= sbt_field_size[i];
879 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
880 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
883 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
884 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
885 pos.x -= hud_fontsize.x;
890 pos.y += 1.25 * hud_fontsize.y;
894 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
896 TC(bool, is_self); TC(int, pl_number);
898 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
900 vector h_pos = item_pos;
901 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
902 // alternated rows highlighting
904 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
905 else if((sbt_highlight) && (!(pl_number % 2)))
906 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
908 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
910 vector pos = item_pos;
911 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
913 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
915 pos.x += hud_fontsize.x * 0.5;
916 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
917 vector tmp = '0 0 0';
919 PlayerScoreField field;
920 for(i = 0; i < sbt_num_fields; ++i)
922 field = sbt_field[i];
923 if(field == SP_SEPARATOR)
926 if(is_spec && field != SP_NAME && field != SP_PING) {
927 pos.x += sbt_field_size[i] + hud_fontsize.x;
930 str = Scoreboard_GetField(pl, field);
931 str = Scoreboard_FixColumnWidth(i, str);
933 pos.x += sbt_field_size[i] + hud_fontsize.x;
935 if(field == SP_NAME) {
936 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
937 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
939 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
940 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
943 tmp.x = sbt_field_size[i] + hud_fontsize.x;
944 if(sbt_field_icon0 != "")
945 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
946 if(sbt_field_icon1 != "")
947 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
948 if(sbt_field_icon2 != "")
949 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
952 if(sbt_field[i] == SP_SEPARATOR)
954 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
955 for(i = sbt_num_fields-1; i > 0; --i)
957 field = sbt_field[i];
958 if(field == SP_SEPARATOR)
961 if(is_spec && field != SP_NAME && field != SP_PING) {
962 pos.x -= sbt_field_size[i] + hud_fontsize.x;
966 str = Scoreboard_GetField(pl, field);
967 str = Scoreboard_FixColumnWidth(i, str);
969 if(field == SP_NAME) {
970 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
971 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
973 tmp.x = sbt_fixcolumnwidth_len;
974 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
977 tmp.x = sbt_field_size[i];
978 if(sbt_field_icon0 != "")
979 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
980 if(sbt_field_icon1 != "")
981 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
982 if(sbt_field_icon2 != "")
983 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
984 pos.x -= sbt_field_size[i] + hud_fontsize.x;
989 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
992 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
995 vector h_pos = item_pos;
996 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
998 bool complete = (this_team == NUM_SPECTATOR);
1001 if((sbt_highlight) && (!(pl_number % 2)))
1002 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1004 vector pos = item_pos;
1005 pos.x += hud_fontsize.x * 0.5;
1006 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1008 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1010 width_limit -= stringwidth("...", false, hud_fontsize);
1011 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1012 static float max_name_width = 0;
1014 float fieldsize = 0;
1015 float min_fieldsize = 0;
1016 float fieldpadding = hud_fontsize.x * 0.25;
1017 if(this_team == NUM_SPECTATOR)
1019 if(autocvar_hud_panel_scoreboard_spectators_showping)
1020 min_fieldsize = stringwidth("999", false, hud_fontsize);
1022 else if(autocvar_hud_panel_scoreboard_others_showscore)
1023 min_fieldsize = stringwidth("99", false, hud_fontsize);
1024 for(i = 0; pl; pl = pl.sort_next)
1026 if(pl.team != this_team)
1028 if(pl == ignored_pl)
1032 if(this_team == NUM_SPECTATOR)
1034 if(autocvar_hud_panel_scoreboard_spectators_showping)
1035 field = Scoreboard_GetField(pl, SP_PING);
1037 else if(autocvar_hud_panel_scoreboard_others_showscore)
1038 field = Scoreboard_GetField(pl, SP_SCORE);
1040 string str = entcs_GetName(pl.sv_entnum);
1041 if (autocvar_hud_panel_scoreboard_playerid)
1042 str = Scoreboard_AddPlayerId(str, pl);
1043 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1044 float column_width = stringwidth(str, true, hud_fontsize);
1045 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1047 if(column_width > max_name_width)
1048 max_name_width = column_width;
1049 column_width = max_name_width;
1053 fieldsize = stringwidth(field, false, hud_fontsize);
1054 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1057 if(pos.x + column_width > width_limit)
1062 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1067 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1068 pos.y += hud_fontsize.y * 1.25;
1072 vector name_pos = pos;
1073 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1074 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1075 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1078 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1079 h_size.y = hud_fontsize.y;
1080 vector field_pos = pos;
1081 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1082 field_pos.x += column_width - h_size.x;
1084 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1085 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1086 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1090 h_size.x = column_width + hud_fontsize.x * 0.25;
1091 h_size.y = hud_fontsize.y;
1092 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1094 pos.x += column_width;
1095 pos.x += hud_fontsize.x;
1097 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1100 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1102 int max_players = 999;
1103 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1105 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1108 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1109 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1110 height /= team_count;
1113 height -= panel_bg_padding * 2; // - padding
1114 max_players = floor(height / (hud_fontsize.y * 1.25));
1115 if(max_players <= 1)
1117 if(max_players == tm.team_size)
1122 entity me = playerslots[current_player];
1124 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1125 panel_size.y += panel_bg_padding * 2;
1128 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1129 if(panel.current_panel_bg != "0")
1130 end_pos.y += panel_bg_border * 2;
1132 if(panel_bg_padding)
1134 panel_pos += '1 1 0' * panel_bg_padding;
1135 panel_size -= '2 2 0' * panel_bg_padding;
1139 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1143 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1145 pos.y += 1.25 * hud_fontsize.y;
1148 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1150 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1153 // print header row and highlight columns
1154 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1156 // fill the table and draw the rows
1157 bool is_self = false;
1158 bool self_shown = false;
1160 for(pl = players.sort_next; pl; pl = pl.sort_next)
1162 if(pl.team != tm.team)
1164 if(i == max_players - 2 && pl != me)
1166 if(!self_shown && me.team == tm.team)
1168 Scoreboard_DrawItem(pos, rgb, me, true, i);
1170 pos.y += 1.25 * hud_fontsize.y;
1174 if(i >= max_players - 1)
1176 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1179 is_self = (pl.sv_entnum == current_player);
1180 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1183 pos.y += 1.25 * hud_fontsize.y;
1187 panel_size.x += panel_bg_padding * 2; // restore initial width
1191 bool Scoreboard_WouldDraw()
1193 if (MUTATOR_CALLHOOK(DrawScoreboard))
1195 else if (QuickMenu_IsOpened())
1197 else if (HUD_Radar_Clickable())
1199 else if (scoreboard_showscores)
1201 else if (intermission == 1)
1203 else if (intermission == 2)
1205 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1206 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1210 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1215 float average_accuracy;
1216 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1218 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1220 WepSet weapons_stat = WepSet_GetFromStat();
1221 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1222 int disownedcnt = 0;
1224 FOREACH(Weapons, it != WEP_Null, {
1225 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1227 WepSet set = it.m_wepset;
1228 if(it.spawnflags & WEP_TYPE_OTHER)
1233 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1235 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1242 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1243 if (weapon_cnt <= 0) return pos;
1246 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1248 int columns = ceil(weapon_cnt / rows);
1250 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1251 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1252 float height = weapon_height + hud_fontsize.y;
1254 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);
1255 pos.y += 1.25 * hud_fontsize.y;
1256 if(panel.current_panel_bg != "0")
1257 pos.y += panel_bg_border;
1260 panel_size.y = height * rows;
1261 panel_size.y += panel_bg_padding * 2;
1263 float panel_bg_alpha_save = panel_bg_alpha;
1264 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1266 panel_bg_alpha = panel_bg_alpha_save;
1268 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1269 if(panel.current_panel_bg != "0")
1270 end_pos.y += panel_bg_border * 2;
1272 if(panel_bg_padding)
1274 panel_pos += '1 1 0' * panel_bg_padding;
1275 panel_size -= '2 2 0' * panel_bg_padding;
1279 vector tmp = panel_size;
1281 float weapon_width = tmp.x / columns / rows;
1284 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1288 // column highlighting
1289 for (int i = 0; i < columns; ++i)
1291 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);
1294 for (int i = 0; i < rows; ++i)
1295 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1298 average_accuracy = 0;
1299 int weapons_with_stats = 0;
1301 pos.x += weapon_width / 2;
1303 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1306 Accuracy_LoadColors();
1308 float oldposx = pos.x;
1312 FOREACH(Weapons, it != WEP_Null, {
1313 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1315 WepSet set = it.m_wepset;
1316 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1318 if (it.spawnflags & WEP_TYPE_OTHER)
1322 if (weapon_stats >= 0)
1323 weapon_alpha = sbt_fg_alpha;
1325 weapon_alpha = 0.2 * sbt_fg_alpha;
1328 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1330 if (weapon_stats >= 0) {
1331 weapons_with_stats += 1;
1332 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1334 string s = sprintf("%d%%", weapon_stats * 100);
1335 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1337 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1338 rgb = Accuracy_GetColor(weapon_stats);
1340 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1342 tmpos.x += weapon_width * rows;
1343 pos.x += weapon_width * rows;
1344 if (rows == 2 && column == columns - 1) {
1352 if (weapons_with_stats)
1353 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1355 panel_size.x += panel_bg_padding * 2; // restore initial width
1360 bool is_item_filtered(entity it)
1362 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1364 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1367 if (it.instanceOfArmor || it.instanceOfHealth)
1369 int ha_mask = floor(mask) % 10;
1372 default: return false;
1373 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1374 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1375 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1376 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1379 if (it.instanceOfAmmo)
1381 int ammo_mask = floor(mask / 10) % 10;
1382 return (ammo_mask == 1);
1387 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1389 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1391 int disowned_cnt = 0;
1392 int uninteresting_cnt = 0;
1393 IL_EACH(default_order_items, true, {
1394 int q = g_inventory.inv_items[it.m_id];
1395 //q = 1; // debug: display all items
1396 if (is_item_filtered(it))
1397 ++uninteresting_cnt;
1401 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1402 int n = items_cnt - disowned_cnt;
1403 if (n <= 0) return pos;
1405 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1406 int columns = max(6, ceil(n / rows));
1408 float item_height = hud_fontsize.y * 2.3;
1409 float height = item_height + hud_fontsize.y;
1411 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1412 pos.y += 1.25 * hud_fontsize.y;
1413 if(panel.current_panel_bg != "0")
1414 pos.y += panel_bg_border;
1417 panel_size.y = height * rows;
1418 panel_size.y += panel_bg_padding * 2;
1420 float panel_bg_alpha_save = panel_bg_alpha;
1421 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1423 panel_bg_alpha = panel_bg_alpha_save;
1425 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1426 if(panel.current_panel_bg != "0")
1427 end_pos.y += panel_bg_border * 2;
1429 if(panel_bg_padding)
1431 panel_pos += '1 1 0' * panel_bg_padding;
1432 panel_size -= '2 2 0' * panel_bg_padding;
1436 vector tmp = panel_size;
1438 float item_width = tmp.x / columns / rows;
1441 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1445 // column highlighting
1446 for (int i = 0; i < columns; ++i)
1448 drawfill(pos + eX * item_width * rows * i, vec2(item_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1451 for (int i = 0; i < rows; ++i)
1452 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1456 pos.x += item_width / 2;
1458 float oldposx = pos.x;
1462 IL_EACH(default_order_items, !is_item_filtered(it), {
1463 int n = g_inventory.inv_items[it.m_id];
1464 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1465 if (n <= 0) continue;
1466 drawpic_aspect_skin(tmpos, it.m_icon, eX * item_width + eY * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1468 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1469 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1470 tmpos.x += item_width * rows;
1471 pos.x += item_width * rows;
1472 if (rows == 2 && column == columns - 1) {
1480 panel_size.x += panel_bg_padding * 2; // restore initial width
1485 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1487 pos.x += hud_fontsize.x * 0.25;
1488 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1489 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1490 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1492 pos.y += hud_fontsize.y;
1497 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1498 float stat_secrets_found, stat_secrets_total;
1499 float stat_monsters_killed, stat_monsters_total;
1503 // get monster stats
1504 stat_monsters_killed = STAT(MONSTERS_KILLED);
1505 stat_monsters_total = STAT(MONSTERS_TOTAL);
1507 // get secrets stats
1508 stat_secrets_found = STAT(SECRETS_FOUND);
1509 stat_secrets_total = STAT(SECRETS_TOTAL);
1511 // get number of rows
1512 if(stat_secrets_total)
1514 if(stat_monsters_total)
1517 // if no rows, return
1521 // draw table header
1522 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1523 pos.y += 1.25 * hud_fontsize.y;
1524 if(panel.current_panel_bg != "0")
1525 pos.y += panel_bg_border;
1528 panel_size.y = hud_fontsize.y * rows;
1529 panel_size.y += panel_bg_padding * 2;
1532 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1533 if(panel.current_panel_bg != "0")
1534 end_pos.y += panel_bg_border * 2;
1536 if(panel_bg_padding)
1538 panel_pos += '1 1 0' * panel_bg_padding;
1539 panel_size -= '2 2 0' * panel_bg_padding;
1543 vector tmp = panel_size;
1546 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1549 if(stat_monsters_total)
1551 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1552 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1556 if(stat_secrets_total)
1558 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1559 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1562 panel_size.x += panel_bg_padding * 2; // restore initial width
1566 int rankings_rows = 0;
1567 int rankings_columns = 0;
1568 int rankings_cnt = 0;
1569 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1572 RANKINGS_RECEIVED_CNT = 0;
1573 for (i=RANKINGS_CNT-1; i>=0; --i)
1575 ++RANKINGS_RECEIVED_CNT;
1577 if (RANKINGS_RECEIVED_CNT == 0)
1580 vector hl_rgb = rgb + '0.5 0.5 0.5';
1582 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1583 pos.y += 1.25 * hud_fontsize.y;
1584 if(panel.current_panel_bg != "0")
1585 pos.y += panel_bg_border;
1590 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1592 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1597 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1599 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1603 float ranksize = 3 * hud_fontsize.x;
1604 float timesize = 5 * hud_fontsize.x;
1605 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1606 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1607 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1610 rankings_cnt = RANKINGS_RECEIVED_CNT;
1611 rankings_rows = ceil(rankings_cnt / rankings_columns);
1614 // expand name column to fill the entire row
1615 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1616 namesize += available_space;
1617 columnsize.x += available_space;
1619 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1620 panel_size.y += panel_bg_padding * 2;
1624 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1625 if(panel.current_panel_bg != "0")
1626 end_pos.y += panel_bg_border * 2;
1628 if(panel_bg_padding)
1630 panel_pos += '1 1 0' * panel_bg_padding;
1631 panel_size -= '2 2 0' * panel_bg_padding;
1637 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1639 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1641 int column = 0, j = 0;
1642 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1643 for(i = 0; i < rankings_cnt; ++i)
1650 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1651 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1652 else if(!((j + column) & 1) && sbt_highlight)
1653 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1655 str = count_ordinal(i+1);
1656 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1657 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1658 str = ColorTranslateRGB(grecordholder[i]);
1660 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1661 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1663 pos.y += 1.25 * hud_fontsize.y;
1665 if(j >= rankings_rows)
1669 pos.x += panel_size.x / rankings_columns;
1670 pos.y = panel_pos.y;
1673 strfree(zoned_name_self);
1675 panel_size.x += panel_bg_padding * 2; // restore initial width
1679 float scoreboard_time;
1680 bool have_weapon_stats;
1681 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1683 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1685 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1688 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1689 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1695 if (!have_weapon_stats)
1697 FOREACH(Weapons, it != WEP_Null, {
1698 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1699 if (weapon_stats >= 0)
1701 have_weapon_stats = true;
1705 if (!have_weapon_stats)
1712 bool have_item_stats;
1713 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1715 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1717 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1720 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1721 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1727 if (!have_item_stats)
1729 IL_EACH(default_order_items, true, {
1730 if (!is_item_filtered(it))
1732 int q = g_inventory.inv_items[it.m_id];
1733 //q = 1; // debug: display all items
1736 have_item_stats = true;
1741 if (!have_item_stats)
1748 vector Scoreboard_Spectators_Draw(vector pos) {
1753 for(pl = players.sort_next; pl; pl = pl.sort_next)
1755 if(pl.team == NUM_SPECTATOR)
1757 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1758 if(tm.team == NUM_SPECTATOR)
1760 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1761 draw_beginBoldFont();
1762 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1764 pos.y += 1.25 * hud_fontsize.y;
1766 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1767 pos.y += 1.25 * hud_fontsize.y;
1772 if (str != "") // if there's at least one spectator
1773 pos.y += 0.5 * hud_fontsize.y;
1778 void Scoreboard_Draw()
1780 if(!autocvar__hud_configure)
1782 if(!hud_draw_maximized) return;
1784 // frametime checks allow to toggle the scoreboard even when the game is paused
1785 if(scoreboard_active) {
1786 if (scoreboard_fade_alpha == 0)
1787 scoreboard_time = time;
1788 if(hud_configure_menu_open == 1)
1789 scoreboard_fade_alpha = 1;
1790 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1791 if (scoreboard_fadeinspeed && frametime)
1792 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1794 scoreboard_fade_alpha = 1;
1795 if(hud_fontsize_str != autocvar_hud_fontsize)
1797 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1798 Scoreboard_initFieldSizes();
1799 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1803 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1804 if (scoreboard_fadeoutspeed && frametime)
1805 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1807 scoreboard_fade_alpha = 0;
1810 if (!scoreboard_fade_alpha)
1812 scoreboard_acc_fade_alpha = 0;
1813 scoreboard_itemstats_fade_alpha = 0;
1818 scoreboard_fade_alpha = 0;
1820 if (autocvar_hud_panel_scoreboard_dynamichud)
1823 HUD_Scale_Disable();
1825 if(scoreboard_fade_alpha <= 0)
1827 panel_fade_alpha *= scoreboard_fade_alpha;
1828 HUD_Panel_LoadCvars();
1830 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1831 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1832 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1833 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1834 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1835 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1836 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1838 // don't overlap with con_notify
1839 if(!autocvar__hud_configure)
1840 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1842 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1843 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1844 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1845 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1846 panel_pos.x = scoreboard_left;
1847 panel_size.x = fixed_scoreboard_width;
1849 Scoreboard_UpdatePlayerTeams();
1851 scoreboard_top = panel_pos.y;
1852 vector pos = panel_pos;
1857 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1859 // Begin of Game Info Section
1860 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1861 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1863 // Game Info: Game Type
1864 str = MapInfo_Type_ToText(gametype);
1865 draw_beginBoldFont();
1866 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);
1869 // Game Info: Game Detail
1870 float tl = STAT(TIMELIMIT);
1871 float fl = STAT(FRAGLIMIT);
1872 float ll = STAT(LEADLIMIT);
1873 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1876 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1877 if(!gametype.m_hidelimits)
1882 str = strcat(str, "^7 / "); // delimiter
1885 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1886 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1887 (teamscores_label(ts_primary) == "fastest") ? "" :
1888 TranslateScoresLabel(teamscores_label(ts_primary))));
1892 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1893 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1894 (scores_label(ps_primary) == "fastest") ? "" :
1895 TranslateScoresLabel(scores_label(ps_primary))));
1900 if(tl > 0 || fl > 0)
1903 if (ll_and_fl && fl > 0)
1904 str = strcat(str, "^7 & ");
1906 str = strcat(str, "^7 / ");
1911 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1912 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1913 (teamscores_label(ts_primary) == "fastest") ? "" :
1914 TranslateScoresLabel(teamscores_label(ts_primary))));
1918 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1919 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1920 (scores_label(ps_primary) == "fastest") ? "" :
1921 TranslateScoresLabel(scores_label(ps_primary))));
1926 pos.y += sb_gameinfo_type_fontsize.y;
1927 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
1929 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1930 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1931 // End of Game Info Section
1933 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1934 if(panel.current_panel_bg != "0")
1935 pos.y += panel_bg_border;
1937 // Draw the scoreboard
1938 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1941 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1945 vector panel_bg_color_save = panel_bg_color;
1946 vector team_score_baseoffset;
1947 vector team_size_baseoffset;
1948 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1950 // put team score to the left of scoreboard (and team size to the right)
1951 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1952 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1953 if(panel.current_panel_bg != "0")
1955 team_score_baseoffset.x -= panel_bg_border;
1956 team_size_baseoffset.x += panel_bg_border;
1961 // put team score to the right of scoreboard (and team size to the left)
1962 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1963 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1964 if(panel.current_panel_bg != "0")
1966 team_score_baseoffset.x += panel_bg_border;
1967 team_size_baseoffset.x -= panel_bg_border;
1971 int team_size_total = 0;
1972 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1974 // calculate team size total (sum of all team sizes)
1975 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1976 if(tm.team != NUM_SPECTATOR)
1977 team_size_total += tm.team_size;
1980 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1982 if(tm.team == NUM_SPECTATOR)
1987 draw_beginBoldFont();
1988 vector rgb = Team_ColorRGB(tm.team);
1989 str = ftos(tm.(teamscores(ts_primary)));
1990 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1992 // team score on the left (default)
1993 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1997 // team score on the right
1998 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2000 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2002 // team size (if set to show on the side)
2003 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2005 // calculate the starting position for the whole team size info string
2006 str = sprintf("%d/%d", tm.team_size, team_size_total);
2007 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2009 // team size on the left
2010 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2014 // team size on the right
2015 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2017 str = sprintf("%d", tm.team_size);
2018 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2019 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2020 str = sprintf("/%d", team_size_total);
2021 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2025 // secondary score, e.g. keyhunt
2026 if(ts_primary != ts_secondary)
2028 str = ftos(tm.(teamscores(ts_secondary)));
2029 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2032 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2037 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2040 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2043 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2044 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2045 else if(panel_bg_color_team > 0)
2046 panel_bg_color = rgb * panel_bg_color_team;
2048 panel_bg_color = rgb;
2049 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2051 panel_bg_color = panel_bg_color_save;
2055 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2056 if(tm.team != NUM_SPECTATOR)
2059 // display it anyway
2060 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2063 // draw scoreboard spectators before accuracy and item stats
2064 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2065 pos = Scoreboard_Spectators_Draw(pos);
2068 // draw accuracy and item stats
2069 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2070 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2071 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2072 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2074 // draw scoreboard spectators after accuracy and item stats and before rankings
2075 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2076 pos = Scoreboard_Spectators_Draw(pos);
2079 if(MUTATOR_CALLHOOK(ShowRankings)) {
2080 string ranktitle = M_ARGV(0, string);
2081 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2082 if(race_speedaward) {
2083 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2084 pos.y += 1.25 * hud_fontsize.y;
2086 if(race_speedaward_alltimebest) {
2087 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);
2088 pos.y += 1.25 * hud_fontsize.y;
2090 if (race_speedaward || race_speedaward_alltimebest)
2091 pos.y += 0.25 * hud_fontsize.y;
2092 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2097 // draw scoreboard spectators after rankings
2098 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2099 pos = Scoreboard_Spectators_Draw(pos);
2102 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2104 // draw scoreboard spectators after mapstats
2105 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2106 pos = Scoreboard_Spectators_Draw(pos);
2110 // print information about respawn status
2111 float respawn_time = STAT(RESPAWN_TIME);
2112 if(!intermission && respawn_time)
2114 if(respawn_time < 0)
2116 // a negative number means we are awaiting respawn, time value is still the same
2117 respawn_time *= -1; // remove mark now that we checked it
2119 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2120 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2122 str = sprintf(_("^1Respawning in ^3%s^1..."),
2123 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2124 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2126 count_seconds(ceil(respawn_time - time))
2130 else if(time < respawn_time)
2132 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2133 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2134 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2136 count_seconds(ceil(respawn_time - time))
2140 else if(time >= respawn_time)
2141 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2143 pos.y += 1.2 * hud_fontsize.y;
2144 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2147 pos.y += hud_fontsize.y;
2148 if (scoreboard_fade_alpha < 1)
2149 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2150 else if (pos.y != scoreboard_bottom)
2152 if (pos.y > scoreboard_bottom)
2153 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2155 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2160 if (scoreboard_fade_alpha == 1)
2162 if (scoreboard_bottom > 0.95 * vid_conheight)
2163 rankings_rows = max(1, rankings_rows - 1);
2164 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2165 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2167 rankings_cnt = rankings_rows * rankings_columns;