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()
212 static float update_time;
213 if (time <= update_time)
219 for(pl = players.sort_next; pl; pl = pl.sort_next)
222 int Team = entcs_GetScoreTeam(pl.sv_entnum);
223 if(SetTeam(pl, Team))
226 Scoreboard_UpdatePlayerPos(pl);
230 pl = players.sort_next;
235 print(strcat("PNUM: ", ftos(num), "\n"));
240 int Scoreboard_CompareScore(int vl, int vr, int f)
242 TC(int, vl); TC(int, vr); TC(int, f);
243 if(f & SFL_ZERO_IS_WORST)
245 if(vl == 0 && vr != 0)
247 if(vl != 0 && vr == 0)
251 return IS_INCREASING(f);
253 return IS_DECREASING(f);
257 float Scoreboard_ComparePlayerScores(entity left, entity right)
259 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
260 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
267 if(vl == NUM_SPECTATOR)
269 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
271 if(!left.gotscores && right.gotscores)
278 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
282 if (!fld) fld = ps_primary;
283 else if (ps_secondary == ps_primary) continue;
284 else fld = ps_secondary;
288 fld = sb_extra_sorting_field[i];
289 if (fld == ps_primary || fld == ps_secondary) continue;
293 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
294 if (r >= 0) return r;
297 if (left.sv_entnum < right.sv_entnum)
303 void Scoreboard_UpdatePlayerPos(entity player)
306 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
308 SORT_SWAP(player, ent);
310 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
312 SORT_SWAP(ent, player);
316 float Scoreboard_CompareTeamScores(entity left, entity right)
318 if(left.team == NUM_SPECTATOR)
320 if(right.team == NUM_SPECTATOR)
325 for(int i = -2; i < MAX_TEAMSCORE; ++i)
329 if (fld_idx == -1) fld_idx = ts_primary;
330 else if (ts_secondary == ts_primary) continue;
331 else fld_idx = ts_secondary;
336 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
339 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
340 if (r >= 0) return r;
343 if (left.team < right.team)
349 void Scoreboard_UpdateTeamPos(entity Team)
352 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
354 SORT_SWAP(Team, ent);
356 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
358 SORT_SWAP(ent, Team);
362 void Cmd_Scoreboard_Help()
364 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
365 LOG_HELP(_("Usage:"));
366 LOG_HELP("^2scoreboard_columns_set ^3default");
367 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
368 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
369 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
370 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
371 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
372 LOG_HELP(_("The following field names are recognized (case insensitive):"));
378 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
379 "of game types, then a slash, to make the field show up only in these\n"
380 "or in all but these game types. You can also specify 'all' as a\n"
381 "field to show all fields available for the current game mode."));
384 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
385 "include/exclude ALL teams/noteams game modes."));
388 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
389 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
390 "right of the vertical bar aligned to the right."));
391 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
392 "other gamemodes except DM."));
395 // NOTE: adding a gametype with ? to not warn for an optional field
396 // make sure it's excluded in a previous exclusive rule, if any
397 // otherwise the previous exclusive rule warns anyway
398 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
399 #define SCOREBOARD_DEFAULT_COLUMNS \
400 "ping pl fps name |" \
401 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
402 " -teams,lms/deaths +ft,tdm/deaths" \
404 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
405 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
406 " +tdm,ft,dom,ons,as/teamkills"\
407 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
408 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
409 " +lms/lives +lms/rank" \
410 " +kh/kckills +kh/losses +kh/caps" \
411 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
412 " +as/objectives +nb/faults +nb/goals" \
413 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
414 " +dom/ticks +dom/takes" \
415 " -lms,rc,cts,inv,nb/score"
417 void Cmd_Scoreboard_SetFields(int argc)
422 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
426 return; // do nothing, we don't know gametype and scores yet
428 // sbt_fields uses strunzone on the titles!
429 if(!sbt_field_title[0])
430 for(i = 0; i < MAX_SBT_FIELDS; ++i)
431 sbt_field_title[i] = strzone("(null)");
433 // TODO: re enable with gametype dependant cvars?
434 if(argc < 3) // no arguments provided
435 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
438 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
442 if(argv(2) == "default" || argv(2) == "expand_default")
444 if(argv(2) == "expand_default")
445 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
446 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
448 else if(argv(2) == "all" || argv(2) == "ALL")
450 string s = "ping pl name |"; // scores without label (not really scores)
453 // scores without label
454 s = strcat(s, " ", "sum");
455 s = strcat(s, " ", "kdratio");
456 s = strcat(s, " ", "frags");
458 FOREACH(Scores, true, {
460 if(it != ps_secondary)
461 if(scores_label(it) != "")
462 s = strcat(s, " ", scores_label(it));
464 if(ps_secondary != ps_primary)
465 s = strcat(s, " ", scores_label(ps_secondary));
466 s = strcat(s, " ", scores_label(ps_primary));
467 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
474 hud_fontsize = HUD_GetFontsize("hud_fontsize");
476 for(i = 1; i < argc - 1; ++i)
479 bool nocomplain = false;
480 if(substring(str, 0, 1) == "?")
483 str = substring(str, 1, strlen(str) - 1);
486 slash = strstrofs(str, "/", 0);
489 pattern = substring(str, 0, slash);
490 str = substring(str, slash + 1, strlen(str) - (slash + 1));
492 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
496 str = strtolower(str);
497 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
498 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
503 // fields without a label (not networked via the score system)
504 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
505 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
506 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
507 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
508 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
509 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
510 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
511 default: // fields with a label
513 // map alternative labels
514 if (str == "damage") str = "dmg";
515 if (str == "damagetaken") str = "dmgtaken";
517 FOREACH(Scores, true, {
518 if (str == strtolower(scores_label(it))) {
520 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
524 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
525 if(!nocomplain && str != "fps") // server can disable the fps field
526 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
528 strfree(sbt_field_title[sbt_num_fields]);
529 sbt_field_size[sbt_num_fields] = 0;
533 sbt_field[sbt_num_fields] = j;
536 if(j == ps_secondary)
537 have_secondary = true;
542 if(sbt_num_fields >= MAX_SBT_FIELDS)
546 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
548 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
549 have_secondary = true;
550 if(ps_primary == ps_secondary)
551 have_secondary = true;
552 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
554 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
558 strfree(sbt_field_title[sbt_num_fields]);
559 for(i = sbt_num_fields; i > 0; --i)
561 sbt_field_title[i] = sbt_field_title[i-1];
562 sbt_field_size[i] = sbt_field_size[i-1];
563 sbt_field[i] = sbt_field[i-1];
565 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
566 sbt_field[0] = SP_NAME;
568 LOG_INFO("fixed missing field 'name'");
572 strfree(sbt_field_title[sbt_num_fields]);
573 for(i = sbt_num_fields; i > 1; --i)
575 sbt_field_title[i] = sbt_field_title[i-1];
576 sbt_field_size[i] = sbt_field_size[i-1];
577 sbt_field[i] = sbt_field[i-1];
579 sbt_field_title[1] = strzone("|");
580 sbt_field[1] = SP_SEPARATOR;
581 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
583 LOG_INFO("fixed missing field '|'");
586 else if(!have_separator)
588 strcpy(sbt_field_title[sbt_num_fields], "|");
589 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
590 sbt_field[sbt_num_fields] = SP_SEPARATOR;
592 LOG_INFO("fixed missing field '|'");
596 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
597 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
598 sbt_field[sbt_num_fields] = ps_secondary;
600 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
604 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
605 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
606 sbt_field[sbt_num_fields] = ps_primary;
608 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
612 sbt_field[sbt_num_fields] = SP_END;
615 string Scoreboard_AddPlayerId(string pl_name, entity pl)
617 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
618 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
619 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
623 vector sbt_field_rgb;
624 string sbt_field_icon0;
625 string sbt_field_icon1;
626 string sbt_field_icon2;
627 vector sbt_field_icon0_rgb;
628 vector sbt_field_icon1_rgb;
629 vector sbt_field_icon2_rgb;
630 string Scoreboard_GetName(entity pl)
632 if(ready_waiting && pl.ready)
634 sbt_field_icon0 = "gfx/scoreboard/player_ready";
638 int f = entcs_GetClientColors(pl.sv_entnum);
640 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
641 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
642 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
643 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
644 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
647 return entcs_GetName(pl.sv_entnum);
650 string Scoreboard_GetField(entity pl, PlayerScoreField field)
652 float tmp, num, denom;
655 sbt_field_rgb = '1 1 1';
656 sbt_field_icon0 = "";
657 sbt_field_icon1 = "";
658 sbt_field_icon2 = "";
659 sbt_field_icon0_rgb = '1 1 1';
660 sbt_field_icon1_rgb = '1 1 1';
661 sbt_field_icon2_rgb = '1 1 1';
666 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
667 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
671 tmp = max(0, min(220, f-80)) / 220;
672 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
678 f = pl.ping_packetloss;
679 tmp = pl.ping_movementloss;
680 if(f == 0 && tmp == 0)
682 str = ftos(ceil(f * 100));
684 str = strcat(str, "~", ftos(ceil(tmp * 100)));
685 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
686 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
690 str = Scoreboard_GetName(pl);
691 if (autocvar_hud_panel_scoreboard_playerid)
692 str = Scoreboard_AddPlayerId(str, pl);
696 f = pl.(scores(SP_KILLS));
697 f -= pl.(scores(SP_SUICIDES));
701 num = pl.(scores(SP_KILLS));
702 denom = pl.(scores(SP_DEATHS));
705 sbt_field_rgb = '0 1 0';
706 str = sprintf("%d", num);
707 } else if(num <= 0) {
708 sbt_field_rgb = '1 0 0';
709 str = sprintf("%.1f", num/denom);
711 str = sprintf("%.1f", num/denom);
715 f = pl.(scores(SP_KILLS));
716 f -= pl.(scores(SP_DEATHS));
719 sbt_field_rgb = '0 1 0';
721 sbt_field_rgb = '1 1 1';
723 sbt_field_rgb = '1 0 0';
729 float elo = pl.(scores(SP_ELO));
731 case -1: return "...";
732 case -2: return _("N/A");
733 default: return ftos(elo);
739 float fps = pl.(scores(SP_FPS));
742 sbt_field_rgb = '1 1 1';
743 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
745 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
746 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
750 case SP_DMG: case SP_DMGTAKEN:
751 return sprintf("%.1f k", pl.(scores(field)) / 1000);
753 default: case SP_SCORE:
754 tmp = pl.(scores(field));
755 f = scores_flags(field);
756 if(field == ps_primary)
757 sbt_field_rgb = '1 1 0';
758 else if(field == ps_secondary)
759 sbt_field_rgb = '0 1 1';
761 sbt_field_rgb = '1 1 1';
762 return ScoreString(f, tmp);
767 float sbt_fixcolumnwidth_len;
768 float sbt_fixcolumnwidth_iconlen;
769 float sbt_fixcolumnwidth_marginlen;
771 string Scoreboard_FixColumnWidth(int i, string str)
777 sbt_fixcolumnwidth_iconlen = 0;
779 if(sbt_field_icon0 != "")
781 sz = draw_getimagesize(sbt_field_icon0);
783 if(sbt_fixcolumnwidth_iconlen < f)
784 sbt_fixcolumnwidth_iconlen = f;
787 if(sbt_field_icon1 != "")
789 sz = draw_getimagesize(sbt_field_icon1);
791 if(sbt_fixcolumnwidth_iconlen < f)
792 sbt_fixcolumnwidth_iconlen = f;
795 if(sbt_field_icon2 != "")
797 sz = draw_getimagesize(sbt_field_icon2);
799 if(sbt_fixcolumnwidth_iconlen < f)
800 sbt_fixcolumnwidth_iconlen = f;
803 if(sbt_fixcolumnwidth_iconlen != 0)
805 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
806 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
809 sbt_fixcolumnwidth_marginlen = 0;
811 if(sbt_field[i] == SP_NAME) // name gets all remaining space
814 float remaining_space = 0;
815 for(j = 0; j < sbt_num_fields; ++j)
817 if (sbt_field[i] != SP_SEPARATOR)
818 remaining_space += sbt_field_size[j] + hud_fontsize.x;
819 sbt_field_size[i] = panel_size.x - remaining_space;
821 if (sbt_fixcolumnwidth_iconlen != 0)
822 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
823 float namesize = panel_size.x - remaining_space;
824 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
825 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
827 max_namesize = vid_conwidth - remaining_space;
830 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
832 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
833 if(sbt_field_size[i] < f)
834 sbt_field_size[i] = f;
839 void Scoreboard_initFieldSizes()
841 for(int i = 0; i < sbt_num_fields; ++i)
843 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
844 Scoreboard_FixColumnWidth(i, "");
848 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
851 vector column_dim = eY * panel_size.y;
853 column_dim.y -= 1.25 * hud_fontsize.y;
854 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
855 pos.x += hud_fontsize.x * 0.5;
856 for(i = 0; i < sbt_num_fields; ++i)
858 if(sbt_field[i] == SP_SEPARATOR)
860 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
863 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
864 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
865 pos.x += column_dim.x;
867 if(sbt_field[i] == SP_SEPARATOR)
869 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
870 for(i = sbt_num_fields - 1; i > 0; --i)
872 if(sbt_field[i] == SP_SEPARATOR)
875 pos.x -= sbt_field_size[i];
880 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
881 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
884 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
885 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
886 pos.x -= hud_fontsize.x;
891 pos.y += 1.25 * hud_fontsize.y;
895 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
897 TC(bool, is_self); TC(int, pl_number);
899 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
901 vector h_pos = item_pos;
902 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
903 // alternated rows highlighting
905 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
906 else if((sbt_highlight) && (!(pl_number % 2)))
907 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
909 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
911 vector pos = item_pos;
912 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
914 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
916 pos.x += hud_fontsize.x * 0.5;
917 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
918 vector tmp = '0 0 0';
920 PlayerScoreField field;
921 for(i = 0; i < sbt_num_fields; ++i)
923 field = sbt_field[i];
924 if(field == SP_SEPARATOR)
927 if(is_spec && field != SP_NAME && field != SP_PING) {
928 pos.x += sbt_field_size[i] + hud_fontsize.x;
931 str = Scoreboard_GetField(pl, field);
932 str = Scoreboard_FixColumnWidth(i, str);
934 pos.x += sbt_field_size[i] + hud_fontsize.x;
936 if(field == SP_NAME) {
937 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
938 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
940 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
941 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 tmp.x = sbt_field_size[i] + hud_fontsize.x;
945 if(sbt_field_icon0 != "")
946 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 if(sbt_field_icon1 != "")
948 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
949 if(sbt_field_icon2 != "")
950 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
953 if(sbt_field[i] == SP_SEPARATOR)
955 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
956 for(i = sbt_num_fields-1; i > 0; --i)
958 field = sbt_field[i];
959 if(field == SP_SEPARATOR)
962 if(is_spec && field != SP_NAME && field != SP_PING) {
963 pos.x -= sbt_field_size[i] + hud_fontsize.x;
967 str = Scoreboard_GetField(pl, field);
968 str = Scoreboard_FixColumnWidth(i, str);
970 if(field == SP_NAME) {
971 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
972 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
974 tmp.x = sbt_fixcolumnwidth_len;
975 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
978 tmp.x = sbt_field_size[i];
979 if(sbt_field_icon0 != "")
980 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
981 if(sbt_field_icon1 != "")
982 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
983 if(sbt_field_icon2 != "")
984 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
985 pos.x -= sbt_field_size[i] + hud_fontsize.x;
990 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
993 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
996 vector h_pos = item_pos;
997 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
999 bool complete = (this_team == NUM_SPECTATOR);
1002 if((sbt_highlight) && (!(pl_number % 2)))
1003 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1005 vector pos = item_pos;
1006 pos.x += hud_fontsize.x * 0.5;
1007 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1009 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1011 width_limit -= stringwidth("...", false, hud_fontsize);
1012 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1013 static float max_name_width = 0;
1015 float fieldsize = 0;
1016 float min_fieldsize = 0;
1017 float fieldpadding = hud_fontsize.x * 0.25;
1018 if(this_team == NUM_SPECTATOR)
1020 if(autocvar_hud_panel_scoreboard_spectators_showping)
1021 min_fieldsize = stringwidth("999", false, hud_fontsize);
1023 else if(autocvar_hud_panel_scoreboard_others_showscore)
1024 min_fieldsize = stringwidth("99", false, hud_fontsize);
1025 for(i = 0; pl; pl = pl.sort_next)
1027 if(pl.team != this_team)
1029 if(pl == ignored_pl)
1033 if(this_team == NUM_SPECTATOR)
1035 if(autocvar_hud_panel_scoreboard_spectators_showping)
1036 field = Scoreboard_GetField(pl, SP_PING);
1038 else if(autocvar_hud_panel_scoreboard_others_showscore)
1039 field = Scoreboard_GetField(pl, SP_SCORE);
1041 string str = entcs_GetName(pl.sv_entnum);
1042 if (autocvar_hud_panel_scoreboard_playerid)
1043 str = Scoreboard_AddPlayerId(str, pl);
1044 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1045 float column_width = stringwidth(str, true, hud_fontsize);
1046 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1048 if(column_width > max_name_width)
1049 max_name_width = column_width;
1050 column_width = max_name_width;
1054 fieldsize = stringwidth(field, false, hud_fontsize);
1055 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1058 if(pos.x + column_width > width_limit)
1063 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1068 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1069 pos.y += hud_fontsize.y * 1.25;
1073 vector name_pos = pos;
1074 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1075 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1076 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1079 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1080 h_size.y = hud_fontsize.y;
1081 vector field_pos = pos;
1082 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1083 field_pos.x += column_width - h_size.x;
1085 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1086 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1087 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1091 h_size.x = column_width + hud_fontsize.x * 0.25;
1092 h_size.y = hud_fontsize.y;
1093 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1095 pos.x += column_width;
1096 pos.x += hud_fontsize.x;
1098 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1101 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1103 int max_players = 999;
1104 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1106 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1109 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1110 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1111 height /= team_count;
1114 height -= panel_bg_padding * 2; // - padding
1115 max_players = floor(height / (hud_fontsize.y * 1.25));
1116 if(max_players <= 1)
1118 if(max_players == tm.team_size)
1123 entity me = playerslots[current_player];
1125 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1126 panel_size.y += panel_bg_padding * 2;
1129 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1130 if(panel.current_panel_bg != "0")
1131 end_pos.y += panel_bg_border * 2;
1133 if(panel_bg_padding)
1135 panel_pos += '1 1 0' * panel_bg_padding;
1136 panel_size -= '2 2 0' * panel_bg_padding;
1140 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1144 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1146 pos.y += 1.25 * hud_fontsize.y;
1149 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1151 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1154 // print header row and highlight columns
1155 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1157 // fill the table and draw the rows
1158 bool is_self = false;
1159 bool self_shown = false;
1161 for(pl = players.sort_next; pl; pl = pl.sort_next)
1163 if(pl.team != tm.team)
1165 if(i == max_players - 2 && pl != me)
1167 if(!self_shown && me.team == tm.team)
1169 Scoreboard_DrawItem(pos, rgb, me, true, i);
1171 pos.y += 1.25 * hud_fontsize.y;
1175 if(i >= max_players - 1)
1177 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1180 is_self = (pl.sv_entnum == current_player);
1181 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1184 pos.y += 1.25 * hud_fontsize.y;
1188 panel_size.x += panel_bg_padding * 2; // restore initial width
1192 bool Scoreboard_WouldDraw()
1194 if (MUTATOR_CALLHOOK(DrawScoreboard))
1196 else if (QuickMenu_IsOpened())
1198 else if (HUD_Radar_Clickable())
1200 else if (scoreboard_showscores)
1202 else if (intermission == 1)
1204 else if (intermission == 2)
1206 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1207 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1211 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1216 float average_accuracy;
1217 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1219 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1221 WepSet weapons_stat = WepSet_GetFromStat();
1222 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1223 int disownedcnt = 0;
1225 FOREACH(Weapons, it != WEP_Null, {
1226 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1228 WepSet set = it.m_wepset;
1229 if(it.spawnflags & WEP_TYPE_OTHER)
1234 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1236 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1243 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1244 if (weapon_cnt <= 0) return pos;
1247 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1249 int columns = ceil(weapon_cnt / rows);
1251 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1252 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1253 float height = weapon_height + hud_fontsize.y;
1255 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);
1256 pos.y += 1.25 * hud_fontsize.y;
1257 if(panel.current_panel_bg != "0")
1258 pos.y += panel_bg_border;
1261 panel_size.y = height * rows;
1262 panel_size.y += panel_bg_padding * 2;
1264 float panel_bg_alpha_save = panel_bg_alpha;
1265 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1267 panel_bg_alpha = panel_bg_alpha_save;
1269 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1270 if(panel.current_panel_bg != "0")
1271 end_pos.y += panel_bg_border * 2;
1273 if(panel_bg_padding)
1275 panel_pos += '1 1 0' * panel_bg_padding;
1276 panel_size -= '2 2 0' * panel_bg_padding;
1280 vector tmp = panel_size;
1282 float weapon_width = tmp.x / columns / rows;
1285 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1289 // column highlighting
1290 for (int i = 0; i < columns; ++i)
1292 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);
1295 for (int i = 0; i < rows; ++i)
1296 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1299 average_accuracy = 0;
1300 int weapons_with_stats = 0;
1302 pos.x += weapon_width / 2;
1304 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1307 Accuracy_LoadColors();
1309 float oldposx = pos.x;
1313 FOREACH(Weapons, it != WEP_Null, {
1314 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1316 WepSet set = it.m_wepset;
1317 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1319 if (it.spawnflags & WEP_TYPE_OTHER)
1323 if (weapon_stats >= 0)
1324 weapon_alpha = sbt_fg_alpha;
1326 weapon_alpha = 0.2 * sbt_fg_alpha;
1329 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1331 if (weapon_stats >= 0) {
1332 weapons_with_stats += 1;
1333 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1335 string s = sprintf("%d%%", weapon_stats * 100);
1336 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1338 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1339 rgb = Accuracy_GetColor(weapon_stats);
1341 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1343 tmpos.x += weapon_width * rows;
1344 pos.x += weapon_width * rows;
1345 if (rows == 2 && column == columns - 1) {
1353 if (weapons_with_stats)
1354 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1356 panel_size.x += panel_bg_padding * 2; // restore initial width
1361 bool is_item_filtered(entity it)
1363 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1365 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1368 if (it.instanceOfArmor || it.instanceOfHealth)
1370 int ha_mask = floor(mask) % 10;
1373 default: return false;
1374 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1375 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1376 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1377 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1380 if (it.instanceOfAmmo)
1382 int ammo_mask = floor(mask / 10) % 10;
1383 return (ammo_mask == 1);
1388 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1390 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1392 int disowned_cnt = 0;
1393 int uninteresting_cnt = 0;
1394 IL_EACH(default_order_items, true, {
1395 int q = g_inventory.inv_items[it.m_id];
1396 //q = 1; // debug: display all items
1397 if (is_item_filtered(it))
1398 ++uninteresting_cnt;
1402 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1403 int n = items_cnt - disowned_cnt;
1404 if (n <= 0) return pos;
1406 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1407 int columns = max(6, ceil(n / rows));
1409 float item_height = hud_fontsize.y * 2.3;
1410 float height = item_height + hud_fontsize.y;
1412 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1413 pos.y += 1.25 * hud_fontsize.y;
1414 if(panel.current_panel_bg != "0")
1415 pos.y += panel_bg_border;
1418 panel_size.y = height * rows;
1419 panel_size.y += panel_bg_padding * 2;
1421 float panel_bg_alpha_save = panel_bg_alpha;
1422 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1424 panel_bg_alpha = panel_bg_alpha_save;
1426 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1427 if(panel.current_panel_bg != "0")
1428 end_pos.y += panel_bg_border * 2;
1430 if(panel_bg_padding)
1432 panel_pos += '1 1 0' * panel_bg_padding;
1433 panel_size -= '2 2 0' * panel_bg_padding;
1437 vector tmp = panel_size;
1439 float item_width = tmp.x / columns / rows;
1442 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1446 // column highlighting
1447 for (int i = 0; i < columns; ++i)
1449 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);
1452 for (int i = 0; i < rows; ++i)
1453 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1457 pos.x += item_width / 2;
1459 float oldposx = pos.x;
1463 IL_EACH(default_order_items, !is_item_filtered(it), {
1464 int n = g_inventory.inv_items[it.m_id];
1465 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1466 if (n <= 0) continue;
1467 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);
1469 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1470 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1471 tmpos.x += item_width * rows;
1472 pos.x += item_width * rows;
1473 if (rows == 2 && column == columns - 1) {
1481 panel_size.x += panel_bg_padding * 2; // restore initial width
1486 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1488 pos.x += hud_fontsize.x * 0.25;
1489 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1490 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1491 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1493 pos.y += hud_fontsize.y;
1498 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1499 float stat_secrets_found, stat_secrets_total;
1500 float stat_monsters_killed, stat_monsters_total;
1504 // get monster stats
1505 stat_monsters_killed = STAT(MONSTERS_KILLED);
1506 stat_monsters_total = STAT(MONSTERS_TOTAL);
1508 // get secrets stats
1509 stat_secrets_found = STAT(SECRETS_FOUND);
1510 stat_secrets_total = STAT(SECRETS_TOTAL);
1512 // get number of rows
1513 if(stat_secrets_total)
1515 if(stat_monsters_total)
1518 // if no rows, return
1522 // draw table header
1523 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1524 pos.y += 1.25 * hud_fontsize.y;
1525 if(panel.current_panel_bg != "0")
1526 pos.y += panel_bg_border;
1529 panel_size.y = hud_fontsize.y * rows;
1530 panel_size.y += panel_bg_padding * 2;
1533 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1534 if(panel.current_panel_bg != "0")
1535 end_pos.y += panel_bg_border * 2;
1537 if(panel_bg_padding)
1539 panel_pos += '1 1 0' * panel_bg_padding;
1540 panel_size -= '2 2 0' * panel_bg_padding;
1544 vector tmp = panel_size;
1547 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1550 if(stat_monsters_total)
1552 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1553 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1557 if(stat_secrets_total)
1559 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1560 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1563 panel_size.x += panel_bg_padding * 2; // restore initial width
1567 int rankings_rows = 0;
1568 int rankings_columns = 0;
1569 int rankings_cnt = 0;
1570 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1573 RANKINGS_RECEIVED_CNT = 0;
1574 for (i=RANKINGS_CNT-1; i>=0; --i)
1576 ++RANKINGS_RECEIVED_CNT;
1578 if (RANKINGS_RECEIVED_CNT == 0)
1581 vector hl_rgb = rgb + '0.5 0.5 0.5';
1583 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1584 pos.y += 1.25 * hud_fontsize.y;
1585 if(panel.current_panel_bg != "0")
1586 pos.y += panel_bg_border;
1591 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1593 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1598 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1600 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1604 float ranksize = 3 * hud_fontsize.x;
1605 float timesize = 5 * hud_fontsize.x;
1606 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1607 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1608 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1611 rankings_cnt = RANKINGS_RECEIVED_CNT;
1612 rankings_rows = ceil(rankings_cnt / rankings_columns);
1615 // expand name column to fill the entire row
1616 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1617 namesize += available_space;
1618 columnsize.x += available_space;
1620 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1621 panel_size.y += panel_bg_padding * 2;
1625 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1626 if(panel.current_panel_bg != "0")
1627 end_pos.y += panel_bg_border * 2;
1629 if(panel_bg_padding)
1631 panel_pos += '1 1 0' * panel_bg_padding;
1632 panel_size -= '2 2 0' * panel_bg_padding;
1638 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1640 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1642 int column = 0, j = 0;
1643 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1644 for(i = 0; i < rankings_cnt; ++i)
1651 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1652 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1653 else if(!((j + column) & 1) && sbt_highlight)
1654 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1656 str = count_ordinal(i+1);
1657 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1658 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1659 str = ColorTranslateRGB(grecordholder[i]);
1661 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1662 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1664 pos.y += 1.25 * hud_fontsize.y;
1666 if(j >= rankings_rows)
1670 pos.x += panel_size.x / rankings_columns;
1671 pos.y = panel_pos.y;
1674 strfree(zoned_name_self);
1676 panel_size.x += panel_bg_padding * 2; // restore initial width
1680 float scoreboard_time;
1681 bool have_weapon_stats;
1682 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1684 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1686 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1689 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1690 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1696 if (!have_weapon_stats)
1698 FOREACH(Weapons, it != WEP_Null, {
1699 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1700 if (weapon_stats >= 0)
1702 have_weapon_stats = true;
1706 if (!have_weapon_stats)
1713 bool have_item_stats;
1714 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1716 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1718 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1721 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1722 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1728 if (!have_item_stats)
1730 IL_EACH(default_order_items, true, {
1731 if (!is_item_filtered(it))
1733 int q = g_inventory.inv_items[it.m_id];
1734 //q = 1; // debug: display all items
1737 have_item_stats = true;
1742 if (!have_item_stats)
1749 vector Scoreboard_Spectators_Draw(vector pos) {
1754 for(pl = players.sort_next; pl; pl = pl.sort_next)
1756 if(pl.team == NUM_SPECTATOR)
1758 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1759 if(tm.team == NUM_SPECTATOR)
1761 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1762 draw_beginBoldFont();
1763 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1765 pos.y += 1.25 * hud_fontsize.y;
1767 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1768 pos.y += 1.25 * hud_fontsize.y;
1773 if (str != "") // if there's at least one spectator
1774 pos.y += 0.5 * hud_fontsize.y;
1779 void Scoreboard_Draw()
1781 if(!autocvar__hud_configure)
1783 if(!hud_draw_maximized) return;
1785 // frametime checks allow to toggle the scoreboard even when the game is paused
1786 if(scoreboard_active) {
1787 if (scoreboard_fade_alpha == 0)
1788 scoreboard_time = time;
1789 if(hud_configure_menu_open == 1)
1790 scoreboard_fade_alpha = 1;
1791 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1792 if (scoreboard_fadeinspeed && frametime)
1793 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1795 scoreboard_fade_alpha = 1;
1796 if(hud_fontsize_str != autocvar_hud_fontsize)
1798 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1799 Scoreboard_initFieldSizes();
1800 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1804 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1805 if (scoreboard_fadeoutspeed && frametime)
1806 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1808 scoreboard_fade_alpha = 0;
1811 if (!scoreboard_fade_alpha)
1813 scoreboard_acc_fade_alpha = 0;
1814 scoreboard_itemstats_fade_alpha = 0;
1819 scoreboard_fade_alpha = 0;
1821 if (autocvar_hud_panel_scoreboard_dynamichud)
1824 HUD_Scale_Disable();
1826 if(scoreboard_fade_alpha <= 0)
1828 panel_fade_alpha *= scoreboard_fade_alpha;
1829 HUD_Panel_LoadCvars();
1831 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1832 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1833 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1834 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1835 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1836 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1837 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1839 // don't overlap with con_notify
1840 if(!autocvar__hud_configure)
1841 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1843 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1844 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1845 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1846 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1847 panel_pos.x = scoreboard_left;
1848 panel_size.x = fixed_scoreboard_width;
1850 Scoreboard_UpdatePlayerTeams();
1852 scoreboard_top = panel_pos.y;
1853 vector pos = panel_pos;
1858 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1860 // Begin of Game Info Section
1861 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1862 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1864 // Game Info: Game Type
1865 str = MapInfo_Type_ToText(gametype);
1866 draw_beginBoldFont();
1867 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);
1870 // Game Info: Game Detail
1871 float tl = STAT(TIMELIMIT);
1872 float fl = STAT(FRAGLIMIT);
1873 float ll = STAT(LEADLIMIT);
1874 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1877 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1878 if(!gametype.m_hidelimits)
1883 str = strcat(str, "^7 / "); // delimiter
1886 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1887 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1888 (teamscores_label(ts_primary) == "fastest") ? "" :
1889 TranslateScoresLabel(teamscores_label(ts_primary))));
1893 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1894 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1895 (scores_label(ps_primary) == "fastest") ? "" :
1896 TranslateScoresLabel(scores_label(ps_primary))));
1901 if(tl > 0 || fl > 0)
1904 if (ll_and_fl && fl > 0)
1905 str = strcat(str, "^7 & ");
1907 str = strcat(str, "^7 / ");
1912 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1913 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1914 (teamscores_label(ts_primary) == "fastest") ? "" :
1915 TranslateScoresLabel(teamscores_label(ts_primary))));
1919 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1920 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1921 (scores_label(ps_primary) == "fastest") ? "" :
1922 TranslateScoresLabel(scores_label(ps_primary))));
1927 pos.y += sb_gameinfo_type_fontsize.y;
1928 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
1930 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1931 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1932 // End of Game Info Section
1934 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1935 if(panel.current_panel_bg != "0")
1936 pos.y += panel_bg_border;
1938 // Draw the scoreboard
1939 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1942 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1946 vector panel_bg_color_save = panel_bg_color;
1947 vector team_score_baseoffset;
1948 vector team_size_baseoffset;
1949 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1951 // put team score to the left of scoreboard (and team size to the right)
1952 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1953 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1954 if(panel.current_panel_bg != "0")
1956 team_score_baseoffset.x -= panel_bg_border;
1957 team_size_baseoffset.x += panel_bg_border;
1962 // put team score to the right of scoreboard (and team size to the left)
1963 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1964 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1965 if(panel.current_panel_bg != "0")
1967 team_score_baseoffset.x += panel_bg_border;
1968 team_size_baseoffset.x -= panel_bg_border;
1972 int team_size_total = 0;
1973 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1975 // calculate team size total (sum of all team sizes)
1976 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1977 if(tm.team != NUM_SPECTATOR)
1978 team_size_total += tm.team_size;
1981 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1983 if(tm.team == NUM_SPECTATOR)
1988 draw_beginBoldFont();
1989 vector rgb = Team_ColorRGB(tm.team);
1990 str = ftos(tm.(teamscores(ts_primary)));
1991 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1993 // team score on the left (default)
1994 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1998 // team score on the right
1999 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2001 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2003 // team size (if set to show on the side)
2004 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2006 // calculate the starting position for the whole team size info string
2007 str = sprintf("%d/%d", tm.team_size, team_size_total);
2008 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2010 // team size on the left
2011 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2015 // team size on the right
2016 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2018 str = sprintf("%d", tm.team_size);
2019 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2020 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2021 str = sprintf("/%d", team_size_total);
2022 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2026 // secondary score, e.g. keyhunt
2027 if(ts_primary != ts_secondary)
2029 str = ftos(tm.(teamscores(ts_secondary)));
2030 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2033 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2038 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2041 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2044 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2045 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2046 else if(panel_bg_color_team > 0)
2047 panel_bg_color = rgb * panel_bg_color_team;
2049 panel_bg_color = rgb;
2050 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2052 panel_bg_color = panel_bg_color_save;
2056 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2057 if(tm.team != NUM_SPECTATOR)
2060 // display it anyway
2061 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2064 // draw scoreboard spectators before accuracy and item stats
2065 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2066 pos = Scoreboard_Spectators_Draw(pos);
2069 // draw accuracy and item stats
2070 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2071 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2072 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2073 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2075 // draw scoreboard spectators after accuracy and item stats and before rankings
2076 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2077 pos = Scoreboard_Spectators_Draw(pos);
2080 if(MUTATOR_CALLHOOK(ShowRankings)) {
2081 string ranktitle = M_ARGV(0, string);
2082 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2083 if(race_speedaward) {
2084 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2085 pos.y += 1.25 * hud_fontsize.y;
2087 if(race_speedaward_alltimebest) {
2088 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);
2089 pos.y += 1.25 * hud_fontsize.y;
2091 if (race_speedaward || race_speedaward_alltimebest)
2092 pos.y += 0.25 * hud_fontsize.y;
2093 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2098 // draw scoreboard spectators after rankings
2099 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2100 pos = Scoreboard_Spectators_Draw(pos);
2103 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2105 // draw scoreboard spectators after mapstats
2106 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2107 pos = Scoreboard_Spectators_Draw(pos);
2111 // print information about respawn status
2112 float respawn_time = STAT(RESPAWN_TIME);
2116 if(respawn_time < 0)
2118 // a negative number means we are awaiting respawn, time value is still the same
2119 respawn_time *= -1; // remove mark now that we checked it
2121 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2122 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2124 str = sprintf(_("^1Respawning in ^3%s^1..."),
2125 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2126 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2128 count_seconds(ceil(respawn_time - time))
2132 else if(time < respawn_time)
2134 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2135 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2136 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2138 count_seconds(ceil(respawn_time - time))
2142 else if(time >= respawn_time)
2143 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2145 pos.y += 1.2 * hud_fontsize.y;
2146 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2149 pos.y += hud_fontsize.y;
2150 if (scoreboard_fade_alpha < 1)
2151 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2152 else if (pos.y != scoreboard_bottom)
2154 if (pos.y > scoreboard_bottom)
2155 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2157 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2162 if (scoreboard_fade_alpha == 1)
2164 if (scoreboard_bottom > 0.95 * vid_conheight)
2165 rankings_rows = max(1, rankings_rows - 1);
2166 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2167 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2169 rankings_cnt = rankings_rows * rankings_columns;