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_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
111 // mode 0: returns translated label
112 // mode 1: prints name and description of all the labels
113 string Label_getInfo(string label, int mode)
116 label = "bckills"; // first case in the switch
120 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
121 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
122 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")));
123 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
124 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
125 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
126 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
127 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
128 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
129 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
130 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
131 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
132 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
133 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
134 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
135 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
136 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
137 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
138 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
139 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
140 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
141 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
142 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
143 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
144 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
145 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
146 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
147 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")));
148 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
149 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
150 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
151 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
152 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
153 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
154 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
155 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
156 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
157 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
158 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
159 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
160 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
161 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
162 default: return label;
167 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
168 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
170 #define SB_EXTRA_SORTING_FIELDS 5
171 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
172 void Scoreboard_InitScores()
176 ps_primary = ps_secondary = NULL;
177 ts_primary = ts_secondary = -1;
178 FOREACH(Scores, true, {
179 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
180 if(f == SFL_SORT_PRIO_PRIMARY)
182 if(f == SFL_SORT_PRIO_SECONDARY)
184 if(ps_primary == it || ps_secondary == it)
186 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
187 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
188 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
189 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
190 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
192 if(ps_secondary == NULL)
193 ps_secondary = ps_primary;
195 for(i = 0; i < MAX_TEAMSCORE; ++i)
197 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
198 if(f == SFL_SORT_PRIO_PRIMARY)
200 if(f == SFL_SORT_PRIO_SECONDARY)
203 if(ts_secondary == -1)
204 ts_secondary = ts_primary;
206 Cmd_Scoreboard_SetFields(0);
210 void Scoreboard_UpdatePlayerTeams()
214 for(pl = players.sort_next; pl; pl = pl.sort_next)
217 int Team = entcs_GetScoreTeam(pl.sv_entnum);
218 if(SetTeam(pl, Team))
221 Scoreboard_UpdatePlayerPos(pl);
225 pl = players.sort_next;
230 print(strcat("PNUM: ", ftos(num), "\n"));
235 int Scoreboard_CompareScore(int vl, int vr, int f)
237 TC(int, vl); TC(int, vr); TC(int, f);
238 if(f & SFL_ZERO_IS_WORST)
240 if(vl == 0 && vr != 0)
242 if(vl != 0 && vr == 0)
246 return IS_INCREASING(f);
248 return IS_DECREASING(f);
252 float Scoreboard_ComparePlayerScores(entity left, entity right)
254 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
255 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
262 if(vl == NUM_SPECTATOR)
264 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
266 if(!left.gotscores && right.gotscores)
273 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
277 if (!fld) fld = ps_primary;
278 else if (ps_secondary == ps_primary) continue;
279 else fld = ps_secondary;
283 fld = sb_extra_sorting_field[i];
284 if (fld == ps_primary || fld == ps_secondary) continue;
288 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
289 if (r >= 0) return r;
292 if (left.sv_entnum < right.sv_entnum)
298 void Scoreboard_UpdatePlayerPos(entity player)
301 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
303 SORT_SWAP(player, ent);
305 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
307 SORT_SWAP(ent, player);
311 float Scoreboard_CompareTeamScores(entity left, entity right)
313 if(left.team == NUM_SPECTATOR)
315 if(right.team == NUM_SPECTATOR)
320 for(int i = -2; i < MAX_TEAMSCORE; ++i)
324 if (fld_idx == -1) fld_idx = ts_primary;
325 else if (ts_secondary == ts_primary) continue;
326 else fld_idx = ts_secondary;
331 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
334 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
335 if (r >= 0) return r;
338 if (left.team < right.team)
344 void Scoreboard_UpdateTeamPos(entity Team)
347 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
349 SORT_SWAP(Team, ent);
351 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
353 SORT_SWAP(ent, Team);
357 void Cmd_Scoreboard_Help()
359 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
360 LOG_HELP(_("Usage:"));
361 LOG_HELP("^2scoreboard_columns_set ^3default");
362 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
363 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
364 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
365 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
366 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
367 LOG_HELP(_("The following field names are recognized (case insensitive):"));
373 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
374 "of game types, then a slash, to make the field show up only in these\n"
375 "or in all but these game types. You can also specify 'all' as a\n"
376 "field to show all fields available for the current game mode."));
379 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
380 "include/exclude ALL teams/noteams game modes."));
383 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
384 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
385 "right of the vertical bar aligned to the right."));
386 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
387 "other gamemodes except DM."));
390 // NOTE: adding a gametype with ? to not warn for an optional field
391 // make sure it's excluded in a previous exclusive rule, if any
392 // otherwise the previous exclusive rule warns anyway
393 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
394 #define SCOREBOARD_DEFAULT_COLUMNS \
395 "ping pl fps name |" \
396 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
397 " -teams,lms/deaths +ft,tdm/deaths" \
399 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
400 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
401 " +tdm,ft,dom,ons,as/teamkills"\
402 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
403 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
404 " +lms/lives +lms/rank" \
405 " +kh/kckills +kh/losses +kh/caps" \
406 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
407 " +as/objectives +nb/faults +nb/goals" \
408 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
409 " +dom/ticks +dom/takes" \
410 " -lms,rc,cts,inv,nb/score"
412 void Cmd_Scoreboard_SetFields(int argc)
417 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
421 return; // do nothing, we don't know gametype and scores yet
423 // sbt_fields uses strunzone on the titles!
424 if(!sbt_field_title[0])
425 for(i = 0; i < MAX_SBT_FIELDS; ++i)
426 sbt_field_title[i] = strzone("(null)");
428 // TODO: re enable with gametype dependant cvars?
429 if(argc < 3) // no arguments provided
430 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
433 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
437 if(argv(2) == "default" || argv(2) == "expand_default")
439 if(argv(2) == "expand_default")
440 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
441 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
443 else if(argv(2) == "all" || argv(2) == "ALL")
445 string s = "ping pl name |"; // scores without label (not really scores)
448 // scores without label
449 s = strcat(s, " ", "sum");
450 s = strcat(s, " ", "kdratio");
451 s = strcat(s, " ", "frags");
453 FOREACH(Scores, true, {
455 if(it != ps_secondary)
456 if(scores_label(it) != "")
457 s = strcat(s, " ", scores_label(it));
459 if(ps_secondary != ps_primary)
460 s = strcat(s, " ", scores_label(ps_secondary));
461 s = strcat(s, " ", scores_label(ps_primary));
462 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
469 hud_fontsize = HUD_GetFontsize("hud_fontsize");
471 for(i = 1; i < argc - 1; ++i)
474 bool nocomplain = false;
475 if(substring(str, 0, 1) == "?")
478 str = substring(str, 1, strlen(str) - 1);
481 slash = strstrofs(str, "/", 0);
484 pattern = substring(str, 0, slash);
485 str = substring(str, slash + 1, strlen(str) - (slash + 1));
487 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
491 str = strtolower(str);
492 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
493 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
498 // fields without a label (not networked via the score system)
499 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
500 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
501 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
502 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
503 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
504 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
505 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
506 default: // fields with a label
508 // map alternative labels
509 if (str == "damage") str = "dmg";
510 if (str == "damagetaken") str = "dmgtaken";
512 FOREACH(Scores, true, {
513 if (str == strtolower(scores_label(it))) {
515 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
519 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
520 if(!nocomplain && str != "fps") // server can disable the fps field
521 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
523 strfree(sbt_field_title[sbt_num_fields]);
524 sbt_field_size[sbt_num_fields] = 0;
528 sbt_field[sbt_num_fields] = j;
531 if(j == ps_secondary)
532 have_secondary = true;
537 if(sbt_num_fields >= MAX_SBT_FIELDS)
541 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
543 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
544 have_secondary = true;
545 if(ps_primary == ps_secondary)
546 have_secondary = true;
547 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
549 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
553 strfree(sbt_field_title[sbt_num_fields]);
554 for(i = sbt_num_fields; i > 0; --i)
556 sbt_field_title[i] = sbt_field_title[i-1];
557 sbt_field_size[i] = sbt_field_size[i-1];
558 sbt_field[i] = sbt_field[i-1];
560 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
561 sbt_field[0] = SP_NAME;
563 LOG_INFO("fixed missing field 'name'");
567 strfree(sbt_field_title[sbt_num_fields]);
568 for(i = sbt_num_fields; i > 1; --i)
570 sbt_field_title[i] = sbt_field_title[i-1];
571 sbt_field_size[i] = sbt_field_size[i-1];
572 sbt_field[i] = sbt_field[i-1];
574 sbt_field_title[1] = strzone("|");
575 sbt_field[1] = SP_SEPARATOR;
576 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
578 LOG_INFO("fixed missing field '|'");
581 else if(!have_separator)
583 strcpy(sbt_field_title[sbt_num_fields], "|");
584 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
585 sbt_field[sbt_num_fields] = SP_SEPARATOR;
587 LOG_INFO("fixed missing field '|'");
591 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
592 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
593 sbt_field[sbt_num_fields] = ps_secondary;
595 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
599 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
600 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
601 sbt_field[sbt_num_fields] = ps_primary;
603 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
607 sbt_field[sbt_num_fields] = SP_END;
610 string Scoreboard_AddPlayerId(string pl_name, entity pl)
612 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
613 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
614 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
618 vector sbt_field_rgb;
619 string sbt_field_icon0;
620 string sbt_field_icon1;
621 string sbt_field_icon2;
622 vector sbt_field_icon0_rgb;
623 vector sbt_field_icon1_rgb;
624 vector sbt_field_icon2_rgb;
625 string Scoreboard_GetName(entity pl)
627 if(ready_waiting && pl.ready)
629 sbt_field_icon0 = "gfx/scoreboard/player_ready";
633 int f = entcs_GetClientColors(pl.sv_entnum);
635 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
636 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
637 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
638 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
639 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
642 return entcs_GetName(pl.sv_entnum);
645 string Scoreboard_GetField(entity pl, PlayerScoreField field)
647 float tmp, num, denom;
650 sbt_field_rgb = '1 1 1';
651 sbt_field_icon0 = "";
652 sbt_field_icon1 = "";
653 sbt_field_icon2 = "";
654 sbt_field_icon0_rgb = '1 1 1';
655 sbt_field_icon1_rgb = '1 1 1';
656 sbt_field_icon2_rgb = '1 1 1';
661 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
662 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
666 tmp = max(0, min(220, f-80)) / 220;
667 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
673 f = pl.ping_packetloss;
674 tmp = pl.ping_movementloss;
675 if(f == 0 && tmp == 0)
677 str = ftos(ceil(f * 100));
679 str = strcat(str, "~", ftos(ceil(tmp * 100)));
680 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
681 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
685 str = Scoreboard_GetName(pl);
686 if (autocvar_hud_panel_scoreboard_playerid)
687 str = Scoreboard_AddPlayerId(str, pl);
691 f = pl.(scores(SP_KILLS));
692 f -= pl.(scores(SP_SUICIDES));
696 num = pl.(scores(SP_KILLS));
697 denom = pl.(scores(SP_DEATHS));
700 sbt_field_rgb = '0 1 0';
701 str = sprintf("%d", num);
702 } else if(num <= 0) {
703 sbt_field_rgb = '1 0 0';
704 str = sprintf("%.1f", num/denom);
706 str = sprintf("%.1f", num/denom);
710 f = pl.(scores(SP_KILLS));
711 f -= pl.(scores(SP_DEATHS));
714 sbt_field_rgb = '0 1 0';
716 sbt_field_rgb = '1 1 1';
718 sbt_field_rgb = '1 0 0';
724 float elo = pl.(scores(SP_ELO));
726 case -1: return "...";
727 case -2: return _("N/A");
728 default: return ftos(elo);
734 float fps = pl.(scores(SP_FPS));
737 sbt_field_rgb = '1 1 1';
738 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
740 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
741 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
745 case SP_DMG: case SP_DMGTAKEN:
746 return sprintf("%.1f k", pl.(scores(field)) / 1000);
748 default: case SP_SCORE:
749 tmp = pl.(scores(field));
750 f = scores_flags(field);
751 if(field == ps_primary)
752 sbt_field_rgb = '1 1 0';
753 else if(field == ps_secondary)
754 sbt_field_rgb = '0 1 1';
756 sbt_field_rgb = '1 1 1';
757 return ScoreString(f, tmp);
762 float sbt_fixcolumnwidth_len;
763 float sbt_fixcolumnwidth_iconlen;
764 float sbt_fixcolumnwidth_marginlen;
766 string Scoreboard_FixColumnWidth(int i, string str)
772 sbt_fixcolumnwidth_iconlen = 0;
774 if(sbt_field_icon0 != "")
776 sz = draw_getimagesize(sbt_field_icon0);
778 if(sbt_fixcolumnwidth_iconlen < f)
779 sbt_fixcolumnwidth_iconlen = f;
782 if(sbt_field_icon1 != "")
784 sz = draw_getimagesize(sbt_field_icon1);
786 if(sbt_fixcolumnwidth_iconlen < f)
787 sbt_fixcolumnwidth_iconlen = f;
790 if(sbt_field_icon2 != "")
792 sz = draw_getimagesize(sbt_field_icon2);
794 if(sbt_fixcolumnwidth_iconlen < f)
795 sbt_fixcolumnwidth_iconlen = f;
798 if(sbt_fixcolumnwidth_iconlen != 0)
800 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
801 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
804 sbt_fixcolumnwidth_marginlen = 0;
806 if(sbt_field[i] == SP_NAME) // name gets all remaining space
809 float remaining_space = 0;
810 for(j = 0; j < sbt_num_fields; ++j)
812 if (sbt_field[i] != SP_SEPARATOR)
813 remaining_space += sbt_field_size[j] + hud_fontsize.x;
814 sbt_field_size[i] = panel_size.x - remaining_space;
816 if (sbt_fixcolumnwidth_iconlen != 0)
817 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
818 float namesize = panel_size.x - remaining_space;
819 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
820 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
822 max_namesize = vid_conwidth - remaining_space;
825 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
827 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
828 if(sbt_field_size[i] < f)
829 sbt_field_size[i] = f;
834 void Scoreboard_initFieldSizes()
836 for(int i = 0; i < sbt_num_fields; ++i)
838 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
839 Scoreboard_FixColumnWidth(i, "");
843 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
846 vector column_dim = eY * panel_size.y;
848 column_dim.y -= 1.25 * hud_fontsize.y;
849 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
850 pos.x += hud_fontsize.x * 0.5;
851 for(i = 0; i < sbt_num_fields; ++i)
853 if(sbt_field[i] == SP_SEPARATOR)
855 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
858 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
859 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
860 pos.x += column_dim.x;
862 if(sbt_field[i] == SP_SEPARATOR)
864 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
865 for(i = sbt_num_fields - 1; i > 0; --i)
867 if(sbt_field[i] == SP_SEPARATOR)
870 pos.x -= sbt_field_size[i];
875 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
876 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
879 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
880 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
881 pos.x -= hud_fontsize.x;
886 pos.y += 1.25 * hud_fontsize.y;
890 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
892 TC(bool, is_self); TC(int, pl_number);
894 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
896 vector h_pos = item_pos;
897 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
898 // alternated rows highlighting
900 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
901 else if((sbt_highlight) && (!(pl_number % 2)))
902 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
904 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
906 vector pos = item_pos;
907 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
909 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
911 pos.x += hud_fontsize.x * 0.5;
912 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
913 vector tmp = '0 0 0';
915 PlayerScoreField field;
916 for(i = 0; i < sbt_num_fields; ++i)
918 field = sbt_field[i];
919 if(field == SP_SEPARATOR)
922 if(is_spec && field != SP_NAME && field != SP_PING) {
923 pos.x += sbt_field_size[i] + hud_fontsize.x;
926 str = Scoreboard_GetField(pl, field);
927 str = Scoreboard_FixColumnWidth(i, str);
929 pos.x += sbt_field_size[i] + hud_fontsize.x;
931 if(field == SP_NAME) {
932 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
933 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
935 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
936 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 tmp.x = sbt_field_size[i] + hud_fontsize.x;
940 if(sbt_field_icon0 != "")
941 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 if(sbt_field_icon1 != "")
943 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 if(sbt_field_icon2 != "")
945 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
948 if(sbt_field[i] == SP_SEPARATOR)
950 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
951 for(i = sbt_num_fields-1; i > 0; --i)
953 field = sbt_field[i];
954 if(field == SP_SEPARATOR)
957 if(is_spec && field != SP_NAME && field != SP_PING) {
958 pos.x -= sbt_field_size[i] + hud_fontsize.x;
962 str = Scoreboard_GetField(pl, field);
963 str = Scoreboard_FixColumnWidth(i, str);
965 if(field == SP_NAME) {
966 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
967 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
969 tmp.x = sbt_fixcolumnwidth_len;
970 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
973 tmp.x = sbt_field_size[i];
974 if(sbt_field_icon0 != "")
975 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
976 if(sbt_field_icon1 != "")
977 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
978 if(sbt_field_icon2 != "")
979 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
980 pos.x -= sbt_field_size[i] + hud_fontsize.x;
985 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
988 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
991 vector h_pos = item_pos;
992 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
994 bool complete = (this_team == NUM_SPECTATOR);
997 if((sbt_highlight) && (!(pl_number % 2)))
998 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1000 vector pos = item_pos;
1001 pos.x += hud_fontsize.x * 0.5;
1002 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1004 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1006 width_limit -= stringwidth("...", false, hud_fontsize);
1007 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1008 static float max_name_width = 0;
1010 float fieldsize = 0;
1011 float min_fieldsize = 0;
1012 float fieldpadding = hud_fontsize.x * 0.25;
1013 if(this_team == NUM_SPECTATOR)
1015 if(autocvar_hud_panel_scoreboard_spectators_showping)
1016 min_fieldsize = stringwidth("999", false, hud_fontsize);
1018 else if(autocvar_hud_panel_scoreboard_others_showscore)
1019 min_fieldsize = stringwidth("99", false, hud_fontsize);
1020 for(i = 0; pl; pl = pl.sort_next)
1022 if(pl.team != this_team)
1024 if(pl == ignored_pl)
1028 if(this_team == NUM_SPECTATOR)
1030 if(autocvar_hud_panel_scoreboard_spectators_showping)
1031 field = Scoreboard_GetField(pl, SP_PING);
1033 else if(autocvar_hud_panel_scoreboard_others_showscore)
1034 field = Scoreboard_GetField(pl, SP_SCORE);
1036 string str = entcs_GetName(pl.sv_entnum);
1037 if (autocvar_hud_panel_scoreboard_playerid)
1038 str = Scoreboard_AddPlayerId(str, pl);
1039 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1040 float column_width = stringwidth(str, true, hud_fontsize);
1041 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1043 if(column_width > max_name_width)
1044 max_name_width = column_width;
1045 column_width = max_name_width;
1049 fieldsize = stringwidth(field, false, hud_fontsize);
1050 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1053 if(pos.x + column_width > width_limit)
1058 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1064 pos.y += hud_fontsize.y * 1.25;
1068 vector name_pos = pos;
1069 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1070 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1071 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1074 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1075 h_size.y = hud_fontsize.y;
1076 vector field_pos = pos;
1077 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1078 field_pos.x += column_width - h_size.x;
1080 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1081 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1082 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1086 h_size.x = column_width + hud_fontsize.x * 0.25;
1087 h_size.y = hud_fontsize.y;
1088 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1090 pos.x += column_width;
1091 pos.x += hud_fontsize.x;
1093 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1096 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1098 int max_players = 999;
1099 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1101 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1104 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1105 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1106 height /= team_count;
1109 height -= panel_bg_padding * 2; // - padding
1110 max_players = floor(height / (hud_fontsize.y * 1.25));
1111 if(max_players <= 1)
1113 if(max_players == tm.team_size)
1118 entity me = playerslots[current_player];
1120 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1121 panel_size.y += panel_bg_padding * 2;
1124 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1125 if(panel.current_panel_bg != "0")
1126 end_pos.y += panel_bg_border * 2;
1128 if(panel_bg_padding)
1130 panel_pos += '1 1 0' * panel_bg_padding;
1131 panel_size -= '2 2 0' * panel_bg_padding;
1135 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1139 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1141 pos.y += 1.25 * hud_fontsize.y;
1144 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1146 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1149 // print header row and highlight columns
1150 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1152 // fill the table and draw the rows
1153 bool is_self = false;
1154 bool self_shown = false;
1156 for(pl = players.sort_next; pl; pl = pl.sort_next)
1158 if(pl.team != tm.team)
1160 if(i == max_players - 2 && pl != me)
1162 if(!self_shown && me.team == tm.team)
1164 Scoreboard_DrawItem(pos, rgb, me, true, i);
1166 pos.y += 1.25 * hud_fontsize.y;
1170 if(i >= max_players - 1)
1172 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1175 is_self = (pl.sv_entnum == current_player);
1176 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1179 pos.y += 1.25 * hud_fontsize.y;
1183 panel_size.x += panel_bg_padding * 2; // restore initial width
1187 bool Scoreboard_WouldDraw()
1189 if (MUTATOR_CALLHOOK(DrawScoreboard))
1191 else if (QuickMenu_IsOpened())
1193 else if (HUD_Radar_Clickable())
1195 else if (scoreboard_showscores)
1197 else if (intermission == 1)
1199 else if (intermission == 2)
1201 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1202 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1206 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1211 float average_accuracy;
1212 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1214 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1216 WepSet weapons_stat = WepSet_GetFromStat();
1217 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1218 int disownedcnt = 0;
1220 FOREACH(Weapons, it != WEP_Null, {
1221 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1223 WepSet set = it.m_wepset;
1224 if(it.spawnflags & WEP_TYPE_OTHER)
1229 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1231 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1238 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1239 if (weapon_cnt <= 0) return pos;
1242 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1244 int columns = ceil(weapon_cnt / rows);
1246 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1247 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1248 float height = weapon_height + hud_fontsize.y;
1250 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);
1251 pos.y += 1.25 * hud_fontsize.y;
1252 if(panel.current_panel_bg != "0")
1253 pos.y += panel_bg_border;
1256 panel_size.y = height * rows;
1257 panel_size.y += panel_bg_padding * 2;
1259 float panel_bg_alpha_save = panel_bg_alpha;
1260 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1262 panel_bg_alpha = panel_bg_alpha_save;
1264 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1265 if(panel.current_panel_bg != "0")
1266 end_pos.y += panel_bg_border * 2;
1268 if(panel_bg_padding)
1270 panel_pos += '1 1 0' * panel_bg_padding;
1271 panel_size -= '2 2 0' * panel_bg_padding;
1275 vector tmp = panel_size;
1277 float weapon_width = tmp.x / columns / rows;
1280 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1284 // column highlighting
1285 for (int i = 0; i < columns; ++i)
1287 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);
1290 for (int i = 0; i < rows; ++i)
1291 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1294 average_accuracy = 0;
1295 int weapons_with_stats = 0;
1297 pos.x += weapon_width / 2;
1299 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1302 Accuracy_LoadColors();
1304 float oldposx = pos.x;
1308 FOREACH(Weapons, it != WEP_Null, {
1309 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1311 WepSet set = it.m_wepset;
1312 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1314 if (it.spawnflags & WEP_TYPE_OTHER)
1318 if (weapon_stats >= 0)
1319 weapon_alpha = sbt_fg_alpha;
1321 weapon_alpha = 0.2 * sbt_fg_alpha;
1324 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1326 if (weapon_stats >= 0) {
1327 weapons_with_stats += 1;
1328 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1330 string s = sprintf("%d%%", weapon_stats * 100);
1331 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1333 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1334 rgb = Accuracy_GetColor(weapon_stats);
1336 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1338 tmpos.x += weapon_width * rows;
1339 pos.x += weapon_width * rows;
1340 if (rows == 2 && column == columns - 1) {
1348 if (weapons_with_stats)
1349 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1351 panel_size.x += panel_bg_padding * 2; // restore initial width
1356 bool is_item_filtered(entity it)
1358 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1360 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1363 if (it.instanceOfArmor || it.instanceOfHealth)
1365 int ha_mask = floor(mask) % 10;
1368 default: return false;
1369 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1370 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1371 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1372 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1375 if (it.instanceOfAmmo)
1377 int ammo_mask = floor(mask / 10) % 10;
1378 return (ammo_mask == 1);
1383 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1385 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1387 int disowned_cnt = 0;
1388 int uninteresting_cnt = 0;
1389 IL_EACH(default_order_items, true, {
1390 int q = g_inventory.inv_items[it.m_id];
1391 //q = 1; // debug: display all items
1392 if (is_item_filtered(it))
1393 ++uninteresting_cnt;
1397 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1398 int n = items_cnt - disowned_cnt;
1399 if (n <= 0) return pos;
1401 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1402 int columns = max(6, ceil(n / rows));
1404 float item_height = hud_fontsize.y * 2.3;
1405 float height = item_height + hud_fontsize.y;
1407 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1408 pos.y += 1.25 * hud_fontsize.y;
1409 if(panel.current_panel_bg != "0")
1410 pos.y += panel_bg_border;
1413 panel_size.y = height * rows;
1414 panel_size.y += panel_bg_padding * 2;
1416 float panel_bg_alpha_save = panel_bg_alpha;
1417 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1419 panel_bg_alpha = panel_bg_alpha_save;
1421 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1422 if(panel.current_panel_bg != "0")
1423 end_pos.y += panel_bg_border * 2;
1425 if(panel_bg_padding)
1427 panel_pos += '1 1 0' * panel_bg_padding;
1428 panel_size -= '2 2 0' * panel_bg_padding;
1432 vector tmp = panel_size;
1434 float item_width = tmp.x / columns / rows;
1437 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1441 // column highlighting
1442 for (int i = 0; i < columns; ++i)
1444 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);
1447 for (int i = 0; i < rows; ++i)
1448 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1452 pos.x += item_width / 2;
1454 float oldposx = pos.x;
1458 IL_EACH(default_order_items, !is_item_filtered(it), {
1459 int n = g_inventory.inv_items[it.m_id];
1460 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1461 if (n <= 0) continue;
1462 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);
1464 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1465 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1466 tmpos.x += item_width * rows;
1467 pos.x += item_width * rows;
1468 if (rows == 2 && column == columns - 1) {
1476 panel_size.x += panel_bg_padding * 2; // restore initial width
1481 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1483 pos.x += hud_fontsize.x * 0.25;
1484 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1485 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1486 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1488 pos.y += hud_fontsize.y;
1493 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1494 float stat_secrets_found, stat_secrets_total;
1495 float stat_monsters_killed, stat_monsters_total;
1499 // get monster stats
1500 stat_monsters_killed = STAT(MONSTERS_KILLED);
1501 stat_monsters_total = STAT(MONSTERS_TOTAL);
1503 // get secrets stats
1504 stat_secrets_found = STAT(SECRETS_FOUND);
1505 stat_secrets_total = STAT(SECRETS_TOTAL);
1507 // get number of rows
1508 if(stat_secrets_total)
1510 if(stat_monsters_total)
1513 // if no rows, return
1517 // draw table header
1518 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1519 pos.y += 1.25 * hud_fontsize.y;
1520 if(panel.current_panel_bg != "0")
1521 pos.y += panel_bg_border;
1524 panel_size.y = hud_fontsize.y * rows;
1525 panel_size.y += panel_bg_padding * 2;
1528 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1529 if(panel.current_panel_bg != "0")
1530 end_pos.y += panel_bg_border * 2;
1532 if(panel_bg_padding)
1534 panel_pos += '1 1 0' * panel_bg_padding;
1535 panel_size -= '2 2 0' * panel_bg_padding;
1539 vector tmp = panel_size;
1542 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1545 if(stat_monsters_total)
1547 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1548 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1552 if(stat_secrets_total)
1554 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1555 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1558 panel_size.x += panel_bg_padding * 2; // restore initial width
1562 int rankings_rows = 0;
1563 int rankings_columns = 0;
1564 int rankings_cnt = 0;
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 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1603 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1606 rankings_cnt = RANKINGS_RECEIVED_CNT;
1607 rankings_rows = ceil(rankings_cnt / rankings_columns);
1610 // expand name column to fill the entire row
1611 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1612 namesize += available_space;
1613 columnsize.x += available_space;
1615 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1616 panel_size.y += panel_bg_padding * 2;
1620 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1621 if(panel.current_panel_bg != "0")
1622 end_pos.y += panel_bg_border * 2;
1624 if(panel_bg_padding)
1626 panel_pos += '1 1 0' * panel_bg_padding;
1627 panel_size -= '2 2 0' * panel_bg_padding;
1633 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1635 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1637 int column = 0, j = 0;
1638 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1639 for(i = 0; i < rankings_cnt; ++i)
1646 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1647 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1648 else if(!((j + column) & 1) && sbt_highlight)
1649 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1651 str = count_ordinal(i+1);
1652 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1653 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1654 str = ColorTranslateRGB(grecordholder[i]);
1656 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1657 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1659 pos.y += 1.25 * hud_fontsize.y;
1661 if(j >= rankings_rows)
1665 pos.x += panel_size.x / rankings_columns;
1666 pos.y = panel_pos.y;
1669 strfree(zoned_name_self);
1671 panel_size.x += panel_bg_padding * 2; // restore initial width
1675 float scoreboard_time;
1676 bool have_weapon_stats;
1677 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1679 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1681 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1684 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1685 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1691 if (!have_weapon_stats)
1693 FOREACH(Weapons, it != WEP_Null, {
1694 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1695 if (weapon_stats >= 0)
1697 have_weapon_stats = true;
1701 if (!have_weapon_stats)
1708 bool have_item_stats;
1709 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1711 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1713 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1716 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1717 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1723 if (!have_item_stats)
1725 IL_EACH(default_order_items, true, {
1726 if (!is_item_filtered(it))
1728 int q = g_inventory.inv_items[it.m_id];
1729 //q = 1; // debug: display all items
1732 have_item_stats = true;
1737 if (!have_item_stats)
1744 vector Scoreboard_Spectators_Draw(vector pos) {
1749 for(pl = players.sort_next; pl; pl = pl.sort_next)
1751 if(pl.team == NUM_SPECTATOR)
1753 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1754 if(tm.team == NUM_SPECTATOR)
1756 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1757 draw_beginBoldFont();
1758 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1760 pos.y += 1.25 * hud_fontsize.y;
1762 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1763 pos.y += 1.25 * hud_fontsize.y;
1768 if (str != "") // if there's at least one spectator
1769 pos.y += 0.5 * hud_fontsize.y;
1774 void Scoreboard_Draw()
1776 if(!autocvar__hud_configure)
1778 if(!hud_draw_maximized) return;
1780 // frametime checks allow to toggle the scoreboard even when the game is paused
1781 if(scoreboard_active) {
1782 if (scoreboard_fade_alpha == 0)
1783 scoreboard_time = time;
1784 if(hud_configure_menu_open == 1)
1785 scoreboard_fade_alpha = 1;
1786 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1787 if (scoreboard_fadeinspeed && frametime)
1788 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1790 scoreboard_fade_alpha = 1;
1791 if(hud_fontsize_str != autocvar_hud_fontsize)
1793 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1794 Scoreboard_initFieldSizes();
1795 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1799 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1800 if (scoreboard_fadeoutspeed && frametime)
1801 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1803 scoreboard_fade_alpha = 0;
1806 if (!scoreboard_fade_alpha)
1808 scoreboard_acc_fade_alpha = 0;
1809 scoreboard_itemstats_fade_alpha = 0;
1814 scoreboard_fade_alpha = 0;
1816 if (autocvar_hud_panel_scoreboard_dynamichud)
1819 HUD_Scale_Disable();
1821 if(scoreboard_fade_alpha <= 0)
1823 panel_fade_alpha *= scoreboard_fade_alpha;
1824 HUD_Panel_LoadCvars();
1826 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1827 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1828 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1829 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1830 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1831 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1832 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1834 // don't overlap with con_notify
1835 if(!autocvar__hud_configure)
1836 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1838 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1839 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1840 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1841 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1842 panel_pos.x = scoreboard_left;
1843 panel_size.x = fixed_scoreboard_width;
1845 Scoreboard_UpdatePlayerTeams();
1847 scoreboard_top = panel_pos.y;
1848 vector pos = panel_pos;
1853 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1855 // Begin of Game Info Section
1856 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1857 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1859 // Game Info: Game Type
1860 str = MapInfo_Type_ToText(gametype);
1861 draw_beginBoldFont();
1862 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);
1865 // Game Info: Game Detail
1866 float tl = STAT(TIMELIMIT);
1867 float fl = STAT(FRAGLIMIT);
1868 float ll = STAT(LEADLIMIT);
1869 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1872 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1873 if(!gametype.m_hidelimits)
1878 str = strcat(str, "^7 / "); // delimiter
1881 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1882 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1883 (teamscores_label(ts_primary) == "fastest") ? "" :
1884 TranslateScoresLabel(teamscores_label(ts_primary))));
1888 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1889 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1890 (scores_label(ps_primary) == "fastest") ? "" :
1891 TranslateScoresLabel(scores_label(ps_primary))));
1896 if(tl > 0 || fl > 0)
1899 if (ll_and_fl && fl > 0)
1900 str = strcat(str, "^7 & ");
1902 str = strcat(str, "^7 / ");
1907 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1908 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1909 (teamscores_label(ts_primary) == "fastest") ? "" :
1910 TranslateScoresLabel(teamscores_label(ts_primary))));
1914 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1915 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1916 (scores_label(ps_primary) == "fastest") ? "" :
1917 TranslateScoresLabel(scores_label(ps_primary))));
1922 pos.y += sb_gameinfo_type_fontsize.y;
1923 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
1925 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1926 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1927 // End of Game Info Section
1929 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1930 if(panel.current_panel_bg != "0")
1931 pos.y += panel_bg_border;
1933 // Draw the scoreboard
1934 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1937 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1941 vector panel_bg_color_save = panel_bg_color;
1942 vector team_score_baseoffset;
1943 vector team_size_baseoffset;
1944 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1946 // put team score to the left of scoreboard (and team size to the right)
1947 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1948 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1949 if(panel.current_panel_bg != "0")
1951 team_score_baseoffset.x -= panel_bg_border;
1952 team_size_baseoffset.x += panel_bg_border;
1957 // put team score to the right of scoreboard (and team size to the left)
1958 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1959 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1960 if(panel.current_panel_bg != "0")
1962 team_score_baseoffset.x += panel_bg_border;
1963 team_size_baseoffset.x -= panel_bg_border;
1967 int team_size_total = 0;
1968 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1970 // calculate team size total (sum of all team sizes)
1971 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1972 if(tm.team != NUM_SPECTATOR)
1973 team_size_total += tm.team_size;
1976 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1978 if(tm.team == NUM_SPECTATOR)
1983 draw_beginBoldFont();
1984 vector rgb = Team_ColorRGB(tm.team);
1985 str = ftos(tm.(teamscores(ts_primary)));
1986 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1988 // team score on the left (default)
1989 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1993 // team score on the right
1994 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1996 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1998 // team size (if set to show on the side)
1999 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2001 // calculate the starting position for the whole team size info string
2002 str = sprintf("%d/%d", tm.team_size, team_size_total);
2003 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2005 // team size on the left
2006 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2010 // team size on the right
2011 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2013 str = sprintf("%d", tm.team_size);
2014 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2015 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2016 str = sprintf("/%d", team_size_total);
2017 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2021 // secondary score, e.g. keyhunt
2022 if(ts_primary != ts_secondary)
2024 str = ftos(tm.(teamscores(ts_secondary)));
2025 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2028 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2033 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2036 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2039 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2040 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2041 else if(panel_bg_color_team > 0)
2042 panel_bg_color = rgb * panel_bg_color_team;
2044 panel_bg_color = rgb;
2045 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2047 panel_bg_color = panel_bg_color_save;
2051 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2052 if(tm.team != NUM_SPECTATOR)
2055 // display it anyway
2056 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2059 // draw scoreboard spectators before accuracy and item stats
2060 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2061 pos = Scoreboard_Spectators_Draw(pos);
2064 // draw accuracy and item stats
2065 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2066 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2067 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2068 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2070 // draw scoreboard spectators after accuracy and item stats and before rankings
2071 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2072 pos = Scoreboard_Spectators_Draw(pos);
2075 if(MUTATOR_CALLHOOK(ShowRankings)) {
2076 string ranktitle = M_ARGV(0, string);
2077 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2078 if(race_speedaward) {
2079 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2080 pos.y += 1.25 * hud_fontsize.y;
2082 if(race_speedaward_alltimebest) {
2083 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);
2084 pos.y += 1.25 * hud_fontsize.y;
2086 if (race_speedaward || race_speedaward_alltimebest)
2087 pos.y += 0.25 * hud_fontsize.y;
2088 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2093 // draw scoreboard spectators after rankings
2094 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2095 pos = Scoreboard_Spectators_Draw(pos);
2098 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2100 // draw scoreboard spectators after mapstats
2101 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2102 pos = Scoreboard_Spectators_Draw(pos);
2106 // print information about respawn status
2107 float respawn_time = STAT(RESPAWN_TIME);
2111 if(respawn_time < 0)
2113 // a negative number means we are awaiting respawn, time value is still the same
2114 respawn_time *= -1; // remove mark now that we checked it
2116 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2117 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2119 str = sprintf(_("^1Respawning in ^3%s^1..."),
2120 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2121 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2123 count_seconds(ceil(respawn_time - time))
2127 else if(time < respawn_time)
2129 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2130 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2131 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2133 count_seconds(ceil(respawn_time - time))
2137 else if(time >= respawn_time)
2138 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2140 pos.y += 1.2 * hud_fontsize.y;
2141 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2144 pos.y += hud_fontsize.y;
2145 if (scoreboard_fade_alpha < 1)
2146 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2147 else if (pos.y != scoreboard_bottom)
2149 if (pos.y > scoreboard_bottom)
2150 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2152 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2157 if (scoreboard_fade_alpha == 1)
2159 if (scoreboard_bottom > 0.95 * vid_conheight)
2160 rankings_rows = max(1, rankings_rows - 1);
2161 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2162 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2164 rankings_cnt = rankings_rows * rankings_columns;