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/gamemodes/gamemode/br/br.qh>
13 #include <common/minigames/cl_minigames.qh>
14 #include <common/net_linked.qh>
15 #include <common/scores.qh>
16 #include <common/stats.qh>
17 #include <common/teams.qh>
18 #include <common/items/inventory.qh>
22 void Scoreboard_Draw_Export(int fh)
24 // allow saving cvars that aesthetically change the panel into hud skin files
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
27 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
35 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
36 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
38 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
42 const int MAX_SBT_FIELDS = MAX_SCORE;
44 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
45 float sbt_field_size[MAX_SBT_FIELDS + 1];
46 string sbt_field_title[MAX_SBT_FIELDS + 1];
49 string autocvar_hud_fontsize;
50 string hud_fontsize_str;
55 float sbt_fg_alpha_self;
57 float sbt_highlight_alpha;
58 float sbt_highlight_alpha_self;
59 float sbt_highlight_alpha_eliminated;
61 // provide basic panel cvars to old clients
62 // TODO remove them after a future release (0.8.2+)
63 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
64 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
65 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
66 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
67 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
68 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
69 noref string autocvar_hud_panel_scoreboard_bg_border = "";
70 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
72 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
73 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
74 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
75 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
76 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
78 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
79 bool autocvar_hud_panel_scoreboard_table_highlight = true;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
82 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
83 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
84 float autocvar_hud_panel_scoreboard_namesize = 15;
85 float autocvar_hud_panel_scoreboard_team_size_position = 0;
86 float autocvar_hud_panel_scoreboard_spectators_position = 1;
88 bool autocvar_hud_panel_scoreboard_accuracy = true;
89 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
90 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
92 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
94 bool autocvar_hud_panel_scoreboard_itemstats = true;
95 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
96 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
97 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
99 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
101 bool autocvar_hud_panel_scoreboard_dynamichud = false;
103 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
104 bool autocvar_hud_panel_scoreboard_others_showscore = true;
105 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
106 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
107 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
108 bool autocvar_hud_panel_scoreboard_playerid = false;
109 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
110 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
112 // mode 0: returns translated label
113 // mode 1: prints name and description of all the labels
114 string Label_getInfo(string label, int mode)
117 label = "bckills"; // first case in the switch
121 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
122 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
123 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")));
124 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
125 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
126 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
127 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
128 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
129 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
130 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
131 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
132 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
133 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
134 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
135 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
136 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
137 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
138 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
139 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
140 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
141 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
142 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
143 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
144 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
145 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
146 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
147 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
148 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")));
149 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
150 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
151 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
152 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
153 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
154 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
155 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
156 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
157 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
158 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
159 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
160 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
161 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
162 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
163 default: return label;
168 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
169 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
171 #define SB_EXTRA_SORTING_FIELDS 5
172 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
173 void Scoreboard_InitScores()
177 ps_primary = ps_secondary = NULL;
178 ts_primary = ts_secondary = -1;
179 FOREACH(Scores, true, {
180 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
181 if(f == SFL_SORT_PRIO_PRIMARY)
183 if(f == SFL_SORT_PRIO_SECONDARY)
185 if(ps_primary == it || ps_secondary == it)
187 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
188 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
189 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
190 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
191 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
193 if(ps_secondary == NULL)
194 ps_secondary = ps_primary;
196 for(i = 0; i < MAX_TEAMSCORE; ++i)
198 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
199 if(f == SFL_SORT_PRIO_PRIMARY)
201 if(f == SFL_SORT_PRIO_SECONDARY)
204 if(ts_secondary == -1)
205 ts_secondary = ts_primary;
207 Cmd_Scoreboard_SetFields(0);
211 void Scoreboard_UpdatePlayerTeams()
213 static float update_time;
214 if (time <= update_time)
220 for(pl = players.sort_next; pl; pl = pl.sort_next)
223 int Team = entcs_GetScoreTeam(pl.sv_entnum);
224 if(SetTeam(pl, Team))
227 Scoreboard_UpdatePlayerPos(pl);
231 pl = players.sort_next;
236 print(strcat("PNUM: ", ftos(num), "\n"));
241 int Scoreboard_CompareScore(int vl, int vr, int f)
243 TC(int, vl); TC(int, vr); TC(int, f);
244 if(f & SFL_ZERO_IS_WORST)
246 if(vl == 0 && vr != 0)
248 if(vl != 0 && vr == 0)
252 return IS_INCREASING(f);
254 return IS_DECREASING(f);
258 float Scoreboard_ComparePlayerScores(entity left, entity right)
260 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
261 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
268 if(vl == NUM_SPECTATOR)
270 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
272 if(!left.gotscores && right.gotscores)
279 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
283 if (!fld) fld = ps_primary;
284 else if (ps_secondary == ps_primary) continue;
285 else fld = ps_secondary;
289 fld = sb_extra_sorting_field[i];
290 if (fld == ps_primary || fld == ps_secondary) continue;
294 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
295 if (r >= 0) return r;
298 if (left.sv_entnum < right.sv_entnum)
304 void Scoreboard_UpdatePlayerPos(entity player)
307 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
309 SORT_SWAP(player, ent);
311 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
313 SORT_SWAP(ent, player);
317 float Scoreboard_CompareTeamScores(entity left, entity right)
319 if(left.team == NUM_SPECTATOR)
321 if(right.team == NUM_SPECTATOR)
326 for(int i = -2; i < MAX_TEAMSCORE; ++i)
330 if (fld_idx == -1) fld_idx = ts_primary;
331 else if (ts_secondary == ts_primary) continue;
332 else fld_idx = ts_secondary;
337 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
340 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
341 if (r >= 0) return r;
344 if (left.team < right.team)
350 void Scoreboard_UpdateTeamPos(entity Team)
353 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
355 SORT_SWAP(Team, ent);
357 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
359 SORT_SWAP(ent, Team);
363 void Cmd_Scoreboard_Help()
365 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
366 LOG_HELP(_("Usage:"));
367 LOG_HELP("^2scoreboard_columns_set ^3default");
368 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
369 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
370 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
371 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
372 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
373 LOG_HELP(_("The following field names are recognized (case insensitive):"));
379 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
380 "of game types, then a slash, to make the field show up only in these\n"
381 "or in all but these game types. You can also specify 'all' as a\n"
382 "field to show all fields available for the current game mode."));
385 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
386 "include/exclude ALL teams/noteams game modes."));
389 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
390 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
391 "right of the vertical bar aligned to the right."));
392 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
393 "other gamemodes except DM."));
396 // NOTE: adding a gametype with ? to not warn for an optional field
397 // make sure it's excluded in a previous exclusive rule, if any
398 // otherwise the previous exclusive rule warns anyway
399 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
400 #define SCOREBOARD_DEFAULT_COLUMNS \
401 "ping pl fps +br/squad name |" \
402 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
403 " -teams,lms,br/deaths +ft,tdm/deaths" \
405 " -teams,lms,rc,cts,inv,ka,br/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
406 " -cts,dm,tdm,ka,ft,br/frags" /* tdm already has this in "score" */ \
407 " +tdm,ft,dom,ons,as/teamkills"\
408 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
409 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
410 " +lms/lives +lms/rank" \
411 " +kh/kckills +kh/losses +kh/caps" \
412 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
413 " +as/objectives +nb/faults +nb/goals" \
414 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
415 " +dom/ticks +dom/takes" \
416 " -lms,rc,cts,inv,nb,br/score" \
420 void Cmd_Scoreboard_SetFields(int argc)
425 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
429 return; // do nothing, we don't know gametype and scores yet
431 // sbt_fields uses strunzone on the titles!
432 if(!sbt_field_title[0])
433 for(i = 0; i < MAX_SBT_FIELDS; ++i)
434 sbt_field_title[i] = strzone("(null)");
436 // TODO: re enable with gametype dependant cvars?
437 if(argc < 3) // no arguments provided
438 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
441 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
445 if(argv(2) == "default" || argv(2) == "expand_default")
447 if(argv(2) == "expand_default")
448 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
449 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
451 else if(argv(2) == "all" || argv(2) == "ALL")
453 string s = "ping pl name |"; // scores without label (not really scores)
456 // scores without label
457 s = strcat(s, " ", "sum");
458 s = strcat(s, " ", "kdratio");
459 s = strcat(s, " ", "frags");
461 FOREACH(Scores, true, {
463 if(it != ps_secondary)
464 if(scores_label(it) != "")
465 s = strcat(s, " ", scores_label(it));
467 if(ps_secondary != ps_primary)
468 s = strcat(s, " ", scores_label(ps_secondary));
469 s = strcat(s, " ", scores_label(ps_primary));
470 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
477 hud_fontsize = HUD_GetFontsize("hud_fontsize");
479 for(i = 1; i < argc - 1; ++i)
482 bool nocomplain = false;
483 if(substring(str, 0, 1) == "?")
486 str = substring(str, 1, strlen(str) - 1);
489 slash = strstrofs(str, "/", 0);
492 pattern = substring(str, 0, slash);
493 str = substring(str, slash + 1, strlen(str) - (slash + 1));
495 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
499 str = strtolower(str);
500 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
501 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
506 // fields without a label (not networked via the score system)
507 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
508 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
509 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
510 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
511 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
512 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
513 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
514 default: // fields with a label
516 // map alternative labels
517 if (str == "damage") str = "dmg";
518 if (str == "damagetaken") str = "dmgtaken";
520 FOREACH(Scores, true, {
521 if (str == strtolower(scores_label(it))) {
523 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
527 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
528 if(!nocomplain && str != "fps") // server can disable the fps field
529 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
531 strfree(sbt_field_title[sbt_num_fields]);
532 sbt_field_size[sbt_num_fields] = 0;
536 sbt_field[sbt_num_fields] = j;
539 if(j == ps_secondary)
540 have_secondary = true;
545 if(sbt_num_fields >= MAX_SBT_FIELDS)
549 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
551 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
552 have_secondary = true;
553 if(ps_primary == ps_secondary)
554 have_secondary = true;
555 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
557 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
561 strfree(sbt_field_title[sbt_num_fields]);
562 for(i = sbt_num_fields; i > 0; --i)
564 sbt_field_title[i] = sbt_field_title[i-1];
565 sbt_field_size[i] = sbt_field_size[i-1];
566 sbt_field[i] = sbt_field[i-1];
568 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
569 sbt_field[0] = SP_NAME;
571 LOG_INFO("fixed missing field 'name'");
575 strfree(sbt_field_title[sbt_num_fields]);
576 for(i = sbt_num_fields; i > 1; --i)
578 sbt_field_title[i] = sbt_field_title[i-1];
579 sbt_field_size[i] = sbt_field_size[i-1];
580 sbt_field[i] = sbt_field[i-1];
582 sbt_field_title[1] = strzone("|");
583 sbt_field[1] = SP_SEPARATOR;
584 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
586 LOG_INFO("fixed missing field '|'");
589 else if(!have_separator)
591 strcpy(sbt_field_title[sbt_num_fields], "|");
592 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
593 sbt_field[sbt_num_fields] = SP_SEPARATOR;
595 LOG_INFO("fixed missing field '|'");
599 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
600 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
601 sbt_field[sbt_num_fields] = ps_secondary;
603 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
607 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
608 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
609 sbt_field[sbt_num_fields] = ps_primary;
611 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
615 sbt_field[sbt_num_fields] = SP_END;
618 string Scoreboard_AddPlayerId(string pl_name, entity pl)
620 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
621 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
622 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
626 vector sbt_field_rgb;
627 string sbt_field_icon0;
628 string sbt_field_icon1;
629 string sbt_field_icon2;
630 vector sbt_field_icon0_rgb;
631 vector sbt_field_icon1_rgb;
632 vector sbt_field_icon2_rgb;
633 string Scoreboard_GetName(entity pl)
635 if(ready_waiting && pl.ready)
637 sbt_field_icon0 = "gfx/scoreboard/player_ready";
639 else if(!teamplay && !(ISGAMETYPE(BR) && STAT(SQUADCOLORS)))
641 int f = entcs_GetClientColors(pl.sv_entnum);
643 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
644 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
645 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
646 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
647 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
650 return entcs_GetName(pl.sv_entnum);
653 string Scoreboard_GetField(entity pl, PlayerScoreField field)
655 float tmp, num, denom;
658 sbt_field_rgb = '1 1 1';
659 sbt_field_icon0 = "";
660 sbt_field_icon1 = "";
661 sbt_field_icon2 = "";
662 sbt_field_icon0_rgb = '1 1 1';
663 sbt_field_icon1_rgb = '1 1 1';
664 sbt_field_icon2_rgb = '1 1 1';
669 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
670 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
674 tmp = max(0, min(220, f-80)) / 220;
675 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
681 f = pl.ping_packetloss;
682 tmp = pl.ping_movementloss;
683 if(f == 0 && tmp == 0)
685 str = ftos(ceil(f * 100));
687 str = strcat(str, "~", ftos(ceil(tmp * 100)));
688 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
689 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
693 str = Scoreboard_GetName(pl);
694 if (autocvar_hud_panel_scoreboard_playerid)
695 str = Scoreboard_AddPlayerId(str, pl);
699 f = pl.(scores(SP_KILLS));
700 f -= pl.(scores(SP_SUICIDES));
704 num = pl.(scores(SP_KILLS));
705 denom = pl.(scores(SP_DEATHS));
708 sbt_field_rgb = '0 1 0';
709 str = sprintf("%d", num);
710 } else if(num <= 0) {
711 sbt_field_rgb = '1 0 0';
712 str = sprintf("%.1f", num/denom);
714 str = sprintf("%.1f", num/denom);
718 f = pl.(scores(SP_KILLS));
719 f -= pl.(scores(SP_DEATHS));
722 sbt_field_rgb = '0 1 0';
724 sbt_field_rgb = '1 1 1';
726 sbt_field_rgb = '1 0 0';
732 float elo = pl.(scores(SP_ELO));
734 case -1: return "...";
735 case -2: return _("N/A");
736 default: return ftos(elo);
742 float fps = pl.(scores(SP_FPS));
745 sbt_field_rgb = '1 1 1';
746 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
748 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
749 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
753 case SP_DMG: case SP_DMGTAKEN:
754 return sprintf("%.1f k", pl.(scores(field)) / 1000);
756 default: case SP_SCORE:
757 tmp = pl.(scores(field));
758 f = scores_flags(field);
759 if(field == ps_primary)
760 sbt_field_rgb = '1 1 0';
761 else if(field == ps_secondary)
762 sbt_field_rgb = '0 1 1';
764 sbt_field_rgb = '1 1 1';
765 return ScoreString(f, tmp);
770 float sbt_fixcolumnwidth_len;
771 float sbt_fixcolumnwidth_iconlen;
772 float sbt_fixcolumnwidth_marginlen;
774 string Scoreboard_FixColumnWidth(int i, string str)
780 sbt_fixcolumnwidth_iconlen = 0;
782 if(sbt_field_icon0 != "")
784 sz = draw_getimagesize(sbt_field_icon0);
786 if(sbt_fixcolumnwidth_iconlen < f)
787 sbt_fixcolumnwidth_iconlen = f;
790 if(sbt_field_icon1 != "")
792 sz = draw_getimagesize(sbt_field_icon1);
794 if(sbt_fixcolumnwidth_iconlen < f)
795 sbt_fixcolumnwidth_iconlen = f;
798 if(sbt_field_icon2 != "")
800 sz = draw_getimagesize(sbt_field_icon2);
802 if(sbt_fixcolumnwidth_iconlen < f)
803 sbt_fixcolumnwidth_iconlen = f;
806 if(sbt_fixcolumnwidth_iconlen != 0)
808 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
809 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
812 sbt_fixcolumnwidth_marginlen = 0;
814 if(sbt_field[i] == SP_NAME) // name gets all remaining space
817 float remaining_space = 0;
818 for(j = 0; j < sbt_num_fields; ++j)
820 if (sbt_field[i] != SP_SEPARATOR)
821 remaining_space += sbt_field_size[j] + hud_fontsize.x;
822 sbt_field_size[i] = panel_size.x - remaining_space;
824 if (sbt_fixcolumnwidth_iconlen != 0)
825 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
826 float namesize = panel_size.x - remaining_space;
827 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
828 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
830 max_namesize = vid_conwidth - remaining_space;
833 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
835 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
836 if(sbt_field_size[i] < f)
837 sbt_field_size[i] = f;
842 void Scoreboard_initFieldSizes()
844 for(int i = 0; i < sbt_num_fields; ++i)
846 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
847 Scoreboard_FixColumnWidth(i, "");
851 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
854 vector column_dim = eY * panel_size.y;
856 column_dim.y -= 1.25 * hud_fontsize.y;
857 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
858 pos.x += hud_fontsize.x * 0.5;
859 for(i = 0; i < sbt_num_fields; ++i)
861 if(sbt_field[i] == SP_SEPARATOR)
863 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
866 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
867 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
868 pos.x += column_dim.x;
870 if(sbt_field[i] == SP_SEPARATOR)
872 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
873 for(i = sbt_num_fields - 1; i > 0; --i)
875 if(sbt_field[i] == SP_SEPARATOR)
878 pos.x -= sbt_field_size[i];
883 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
884 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
887 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
888 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
889 pos.x -= hud_fontsize.x;
894 pos.y += 1.25 * hud_fontsize.y;
898 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
900 TC(bool, is_self); TC(int, pl_number);
902 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
904 vector h_pos = item_pos;
905 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
906 // alternated rows highlighting
908 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
909 else if((sbt_highlight) && (!(pl_number % 2)))
910 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
912 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
914 vector pos = item_pos;
915 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
917 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
919 pos.x += hud_fontsize.x * 0.5;
920 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
921 vector tmp = '0 0 0';
923 PlayerScoreField field;
924 for(i = 0; i < sbt_num_fields; ++i)
926 field = sbt_field[i];
927 if(field == SP_SEPARATOR)
930 if(is_spec && field != SP_NAME && field != SP_PING) {
931 pos.x += sbt_field_size[i] + hud_fontsize.x;
934 str = Scoreboard_GetField(pl, field);
935 str = Scoreboard_FixColumnWidth(i, str);
937 pos.x += sbt_field_size[i] + hud_fontsize.x;
939 if(field == SP_NAME) {
940 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
941 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
943 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
944 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
947 tmp.x = sbt_field_size[i] + hud_fontsize.x;
948 if(sbt_field_icon0 != "")
949 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
950 if(sbt_field_icon1 != "")
951 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
952 if(sbt_field_icon2 != "")
953 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
956 if(sbt_field[i] == SP_SEPARATOR)
958 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
959 for(i = sbt_num_fields-1; i > 0; --i)
961 field = sbt_field[i];
962 if(field == SP_SEPARATOR)
965 if(is_spec && field != SP_NAME && field != SP_PING) {
966 pos.x -= sbt_field_size[i] + hud_fontsize.x;
970 str = Scoreboard_GetField(pl, field);
971 str = Scoreboard_FixColumnWidth(i, str);
973 if(field == SP_NAME) {
974 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
975 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
977 tmp.x = sbt_fixcolumnwidth_len;
978 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
981 tmp.x = sbt_field_size[i];
982 if(sbt_field_icon0 != "")
983 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
984 if(sbt_field_icon1 != "")
985 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
986 if(sbt_field_icon2 != "")
987 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
988 pos.x -= sbt_field_size[i] + hud_fontsize.x;
993 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
996 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
999 vector h_pos = item_pos;
1000 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1002 bool complete = (this_team == NUM_SPECTATOR);
1005 if((sbt_highlight) && (!(pl_number % 2)))
1006 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1008 vector pos = item_pos;
1009 pos.x += hud_fontsize.x * 0.5;
1010 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1012 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1014 width_limit -= stringwidth("...", false, hud_fontsize);
1015 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1016 static float max_name_width = 0;
1018 float fieldsize = 0;
1019 float min_fieldsize = 0;
1020 float fieldpadding = hud_fontsize.x * 0.25;
1021 if(this_team == NUM_SPECTATOR)
1023 if(autocvar_hud_panel_scoreboard_spectators_showping)
1024 min_fieldsize = stringwidth("999", false, hud_fontsize);
1026 else if(autocvar_hud_panel_scoreboard_others_showscore)
1027 min_fieldsize = stringwidth("99", false, hud_fontsize);
1028 for(i = 0; pl; pl = pl.sort_next)
1030 if(pl.team != this_team)
1032 if(pl == ignored_pl)
1036 if(this_team == NUM_SPECTATOR)
1038 if(autocvar_hud_panel_scoreboard_spectators_showping)
1039 field = Scoreboard_GetField(pl, SP_PING);
1041 else if(autocvar_hud_panel_scoreboard_others_showscore)
1042 field = Scoreboard_GetField(pl, SP_SCORE);
1044 string str = entcs_GetName(pl.sv_entnum);
1045 if (autocvar_hud_panel_scoreboard_playerid)
1046 str = Scoreboard_AddPlayerId(str, pl);
1047 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1048 float column_width = stringwidth(str, true, hud_fontsize);
1049 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1051 if(column_width > max_name_width)
1052 max_name_width = column_width;
1053 column_width = max_name_width;
1057 fieldsize = stringwidth(field, false, hud_fontsize);
1058 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1061 if(pos.x + column_width > width_limit)
1066 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1071 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1072 pos.y += hud_fontsize.y * 1.25;
1076 vector name_pos = pos;
1077 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1078 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1079 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1082 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1083 h_size.y = hud_fontsize.y;
1084 vector field_pos = pos;
1085 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1086 field_pos.x += column_width - h_size.x;
1088 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1089 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1090 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1094 h_size.x = column_width + hud_fontsize.x * 0.25;
1095 h_size.y = hud_fontsize.y;
1096 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1098 pos.x += column_width;
1099 pos.x += hud_fontsize.x;
1101 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1104 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1106 int max_players = 999;
1107 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1109 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1112 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1113 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1114 height /= team_count;
1117 height -= panel_bg_padding * 2; // - padding
1118 max_players = floor(height / (hud_fontsize.y * 1.25));
1119 if(max_players <= 1)
1121 if(max_players == tm.team_size)
1126 entity me = playerslots[current_player];
1128 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1129 panel_size.y += panel_bg_padding * 2;
1132 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1133 if(panel.current_panel_bg != "0")
1134 end_pos.y += panel_bg_border * 2;
1136 if(panel_bg_padding)
1138 panel_pos += '1 1 0' * panel_bg_padding;
1139 panel_size -= '2 2 0' * panel_bg_padding;
1143 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1147 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1149 pos.y += 1.25 * hud_fontsize.y;
1152 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1154 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1157 // print header row and highlight columns
1158 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1160 // fill the table and draw the rows
1161 bool is_self = false;
1162 bool self_shown = false;
1164 for(pl = players.sort_next; pl; pl = pl.sort_next)
1166 if(pl.team != tm.team)
1168 if(i == max_players - 2 && pl != me)
1170 if(!self_shown && me.team == tm.team)
1172 Scoreboard_DrawItem(pos, rgb, me, true, i);
1174 pos.y += 1.25 * hud_fontsize.y;
1178 if(i >= max_players - 1)
1180 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1183 is_self = (pl.sv_entnum == current_player);
1184 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1187 pos.y += 1.25 * hud_fontsize.y;
1191 panel_size.x += panel_bg_padding * 2; // restore initial width
1195 bool Scoreboard_WouldDraw()
1197 if (MUTATOR_CALLHOOK(DrawScoreboard))
1199 else if (QuickMenu_IsOpened())
1201 else if (HUD_Radar_Clickable())
1203 else if (scoreboard_showscores)
1205 else if (intermission == 1)
1207 else if (intermission == 2)
1209 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1210 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1214 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1219 float average_accuracy;
1220 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1222 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1224 WepSet weapons_stat = WepSet_GetFromStat();
1225 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1226 int disownedcnt = 0;
1228 FOREACH(Weapons, it != WEP_Null, {
1229 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1231 WepSet set = it.m_wepset;
1232 if(it.spawnflags & WEP_TYPE_OTHER)
1237 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1239 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1246 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1247 if (weapon_cnt <= 0) return pos;
1250 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1252 int columns = ceil(weapon_cnt / rows);
1254 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1255 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1256 float height = weapon_height + hud_fontsize.y;
1258 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);
1259 pos.y += 1.25 * hud_fontsize.y;
1260 if(panel.current_panel_bg != "0")
1261 pos.y += panel_bg_border;
1264 panel_size.y = height * rows;
1265 panel_size.y += panel_bg_padding * 2;
1267 float panel_bg_alpha_save = panel_bg_alpha;
1268 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1270 panel_bg_alpha = panel_bg_alpha_save;
1272 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1273 if(panel.current_panel_bg != "0")
1274 end_pos.y += panel_bg_border * 2;
1276 if(panel_bg_padding)
1278 panel_pos += '1 1 0' * panel_bg_padding;
1279 panel_size -= '2 2 0' * panel_bg_padding;
1283 vector tmp = panel_size;
1285 float weapon_width = tmp.x / columns / rows;
1288 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1292 // column highlighting
1293 for (int i = 0; i < columns; ++i)
1295 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);
1298 for (int i = 0; i < rows; ++i)
1299 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1302 average_accuracy = 0;
1303 int weapons_with_stats = 0;
1305 pos.x += weapon_width / 2;
1307 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1310 Accuracy_LoadColors();
1312 float oldposx = pos.x;
1316 FOREACH(Weapons, it != WEP_Null, {
1317 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1319 WepSet set = it.m_wepset;
1320 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1322 if (it.spawnflags & WEP_TYPE_OTHER)
1326 if (weapon_stats >= 0)
1327 weapon_alpha = sbt_fg_alpha;
1329 weapon_alpha = 0.2 * sbt_fg_alpha;
1332 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1334 if (weapon_stats >= 0) {
1335 weapons_with_stats += 1;
1336 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1338 string s = sprintf("%d%%", weapon_stats * 100);
1339 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1341 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1342 rgb = Accuracy_GetColor(weapon_stats);
1344 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1346 tmpos.x += weapon_width * rows;
1347 pos.x += weapon_width * rows;
1348 if (rows == 2 && column == columns - 1) {
1356 if (weapons_with_stats)
1357 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1359 panel_size.x += panel_bg_padding * 2; // restore initial width
1364 bool is_item_filtered(entity it)
1366 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1368 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1371 if (it.instanceOfArmor || it.instanceOfHealth)
1373 int ha_mask = floor(mask) % 10;
1376 default: return false;
1377 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1378 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1379 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1380 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1383 if (it.instanceOfAmmo)
1385 int ammo_mask = floor(mask / 10) % 10;
1386 return (ammo_mask == 1);
1391 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1393 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1395 int disowned_cnt = 0;
1396 int uninteresting_cnt = 0;
1397 IL_EACH(default_order_items, true, {
1398 int q = g_inventory.inv_items[it.m_id];
1399 //q = 1; // debug: display all items
1400 if (is_item_filtered(it))
1401 ++uninteresting_cnt;
1405 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1406 int n = items_cnt - disowned_cnt;
1407 if (n <= 0) return pos;
1409 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1410 int columns = max(6, ceil(n / rows));
1412 float item_height = hud_fontsize.y * 2.3;
1413 float height = item_height + hud_fontsize.y;
1415 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1416 pos.y += 1.25 * hud_fontsize.y;
1417 if(panel.current_panel_bg != "0")
1418 pos.y += panel_bg_border;
1421 panel_size.y = height * rows;
1422 panel_size.y += panel_bg_padding * 2;
1424 float panel_bg_alpha_save = panel_bg_alpha;
1425 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1427 panel_bg_alpha = panel_bg_alpha_save;
1429 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1430 if(panel.current_panel_bg != "0")
1431 end_pos.y += panel_bg_border * 2;
1433 if(panel_bg_padding)
1435 panel_pos += '1 1 0' * panel_bg_padding;
1436 panel_size -= '2 2 0' * panel_bg_padding;
1440 vector tmp = panel_size;
1442 float item_width = tmp.x / columns / rows;
1445 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1449 // column highlighting
1450 for (int i = 0; i < columns; ++i)
1452 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);
1455 for (int i = 0; i < rows; ++i)
1456 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1460 pos.x += item_width / 2;
1462 float oldposx = pos.x;
1466 IL_EACH(default_order_items, !is_item_filtered(it), {
1467 int n = g_inventory.inv_items[it.m_id];
1468 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1469 if (n <= 0) continue;
1470 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);
1472 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1473 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1474 tmpos.x += item_width * rows;
1475 pos.x += item_width * rows;
1476 if (rows == 2 && column == columns - 1) {
1484 panel_size.x += panel_bg_padding * 2; // restore initial width
1489 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1491 pos.x += hud_fontsize.x * 0.25;
1492 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1493 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1494 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1496 pos.y += hud_fontsize.y;
1501 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1502 float stat_secrets_found, stat_secrets_total;
1503 float stat_monsters_killed, stat_monsters_total;
1507 // get monster stats
1508 stat_monsters_killed = STAT(MONSTERS_KILLED);
1509 stat_monsters_total = STAT(MONSTERS_TOTAL);
1511 // get secrets stats
1512 stat_secrets_found = STAT(SECRETS_FOUND);
1513 stat_secrets_total = STAT(SECRETS_TOTAL);
1515 // get number of rows
1516 if(stat_secrets_total)
1518 if(stat_monsters_total)
1521 // if no rows, return
1525 // draw table header
1526 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1527 pos.y += 1.25 * hud_fontsize.y;
1528 if(panel.current_panel_bg != "0")
1529 pos.y += panel_bg_border;
1532 panel_size.y = hud_fontsize.y * rows;
1533 panel_size.y += panel_bg_padding * 2;
1536 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1537 if(panel.current_panel_bg != "0")
1538 end_pos.y += panel_bg_border * 2;
1540 if(panel_bg_padding)
1542 panel_pos += '1 1 0' * panel_bg_padding;
1543 panel_size -= '2 2 0' * panel_bg_padding;
1547 vector tmp = panel_size;
1550 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1553 if(stat_monsters_total)
1555 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1556 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1560 if(stat_secrets_total)
1562 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1563 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1566 panel_size.x += panel_bg_padding * 2; // restore initial width
1570 int rankings_rows = 0;
1571 int rankings_columns = 0;
1572 int rankings_cnt = 0;
1573 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1576 RANKINGS_RECEIVED_CNT = 0;
1577 for (i=RANKINGS_CNT-1; i>=0; --i)
1579 ++RANKINGS_RECEIVED_CNT;
1581 if (RANKINGS_RECEIVED_CNT == 0)
1584 vector hl_rgb = rgb + '0.5 0.5 0.5';
1586 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1587 pos.y += 1.25 * hud_fontsize.y;
1588 if(panel.current_panel_bg != "0")
1589 pos.y += panel_bg_border;
1594 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1596 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1601 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1603 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1607 float ranksize = 3 * hud_fontsize.x;
1608 float timesize = 5 * hud_fontsize.x;
1609 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1610 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1611 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1614 rankings_cnt = RANKINGS_RECEIVED_CNT;
1615 rankings_rows = ceil(rankings_cnt / rankings_columns);
1618 // expand name column to fill the entire row
1619 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1620 namesize += available_space;
1621 columnsize.x += available_space;
1623 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1624 panel_size.y += panel_bg_padding * 2;
1628 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1629 if(panel.current_panel_bg != "0")
1630 end_pos.y += panel_bg_border * 2;
1632 if(panel_bg_padding)
1634 panel_pos += '1 1 0' * panel_bg_padding;
1635 panel_size -= '2 2 0' * panel_bg_padding;
1641 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1643 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1645 int column = 0, j = 0;
1646 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1647 for(i = 0; i < rankings_cnt; ++i)
1654 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1655 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1656 else if(!((j + column) & 1) && sbt_highlight)
1657 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1659 str = count_ordinal(i+1);
1660 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1661 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1662 str = ColorTranslateRGB(grecordholder[i]);
1664 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1665 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1667 pos.y += 1.25 * hud_fontsize.y;
1669 if(j >= rankings_rows)
1673 pos.x += panel_size.x / rankings_columns;
1674 pos.y = panel_pos.y;
1677 strfree(zoned_name_self);
1679 panel_size.x += panel_bg_padding * 2; // restore initial width
1683 float scoreboard_time;
1684 bool have_weapon_stats;
1685 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1687 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1689 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1692 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1693 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1699 if (!have_weapon_stats)
1701 FOREACH(Weapons, it != WEP_Null, {
1702 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1703 if (weapon_stats >= 0)
1705 have_weapon_stats = true;
1709 if (!have_weapon_stats)
1716 bool have_item_stats;
1717 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1719 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1721 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1724 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1725 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1731 if (!have_item_stats)
1733 IL_EACH(default_order_items, true, {
1734 if (!is_item_filtered(it))
1736 int q = g_inventory.inv_items[it.m_id];
1737 //q = 1; // debug: display all items
1740 have_item_stats = true;
1745 if (!have_item_stats)
1752 vector Scoreboard_Spectators_Draw(vector pos) {
1757 for(pl = players.sort_next; pl; pl = pl.sort_next)
1759 if(pl.team == NUM_SPECTATOR)
1761 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1762 if(tm.team == NUM_SPECTATOR)
1764 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1765 draw_beginBoldFont();
1766 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1768 pos.y += 1.25 * hud_fontsize.y;
1770 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1771 pos.y += 1.25 * hud_fontsize.y;
1776 if (str != "") // if there's at least one spectator
1777 pos.y += 0.5 * hud_fontsize.y;
1782 void Scoreboard_Draw()
1784 if(!autocvar__hud_configure)
1786 if(!hud_draw_maximized) return;
1788 // frametime checks allow to toggle the scoreboard even when the game is paused
1789 if(scoreboard_active) {
1790 if (scoreboard_fade_alpha == 0)
1791 scoreboard_time = time;
1792 if(hud_configure_menu_open == 1)
1793 scoreboard_fade_alpha = 1;
1794 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1795 if (scoreboard_fadeinspeed && frametime)
1796 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1798 scoreboard_fade_alpha = 1;
1799 if(hud_fontsize_str != autocvar_hud_fontsize)
1801 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1802 Scoreboard_initFieldSizes();
1803 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1807 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1808 if (scoreboard_fadeoutspeed && frametime)
1809 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1811 scoreboard_fade_alpha = 0;
1814 if (!scoreboard_fade_alpha)
1816 scoreboard_acc_fade_alpha = 0;
1817 scoreboard_itemstats_fade_alpha = 0;
1822 scoreboard_fade_alpha = 0;
1824 if (autocvar_hud_panel_scoreboard_dynamichud)
1827 HUD_Scale_Disable();
1829 if(scoreboard_fade_alpha <= 0)
1831 panel_fade_alpha *= scoreboard_fade_alpha;
1832 HUD_Panel_LoadCvars();
1834 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1835 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1836 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1837 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1838 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1839 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1840 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1842 // don't overlap with con_notify
1843 if(!autocvar__hud_configure)
1844 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1846 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1847 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1848 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1849 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1850 panel_pos.x = scoreboard_left;
1851 panel_size.x = fixed_scoreboard_width;
1853 Scoreboard_UpdatePlayerTeams();
1855 scoreboard_top = panel_pos.y;
1856 vector pos = panel_pos;
1861 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1863 // Begin of Game Info Section
1864 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1865 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1867 // Game Info: Game Type
1868 str = MapInfo_Type_ToText(gametype);
1869 draw_beginBoldFont();
1870 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);
1873 // Game Info: Game Detail
1874 float tl = STAT(TIMELIMIT);
1875 float fl = STAT(FRAGLIMIT);
1876 float ll = STAT(LEADLIMIT);
1877 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1880 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1881 if(!gametype.m_hidelimits)
1886 str = strcat(str, "^7 / "); // delimiter
1889 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1890 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1891 (teamscores_label(ts_primary) == "fastest") ? "" :
1892 TranslateScoresLabel(teamscores_label(ts_primary))));
1896 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1897 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1898 (scores_label(ps_primary) == "fastest") ? "" :
1899 TranslateScoresLabel(scores_label(ps_primary))));
1904 if(tl > 0 || fl > 0)
1907 if (ll_and_fl && fl > 0)
1908 str = strcat(str, "^7 & ");
1910 str = strcat(str, "^7 / ");
1915 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1916 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1917 (teamscores_label(ts_primary) == "fastest") ? "" :
1918 TranslateScoresLabel(teamscores_label(ts_primary))));
1922 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1923 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1924 (scores_label(ps_primary) == "fastest") ? "" :
1925 TranslateScoresLabel(scores_label(ps_primary))));
1930 pos.y += sb_gameinfo_type_fontsize.y;
1931 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
1933 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1934 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1935 // End of Game Info Section
1937 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1938 if(panel.current_panel_bg != "0")
1939 pos.y += panel_bg_border;
1941 // Draw the scoreboard
1942 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1945 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1949 vector panel_bg_color_save = panel_bg_color;
1950 vector team_score_baseoffset;
1951 vector team_size_baseoffset;
1952 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1954 // put team score to the left of scoreboard (and team size to the right)
1955 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1956 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1957 if(panel.current_panel_bg != "0")
1959 team_score_baseoffset.x -= panel_bg_border;
1960 team_size_baseoffset.x += panel_bg_border;
1965 // put team score to the right of scoreboard (and team size to the left)
1966 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1967 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1968 if(panel.current_panel_bg != "0")
1970 team_score_baseoffset.x += panel_bg_border;
1971 team_size_baseoffset.x -= panel_bg_border;
1975 int team_size_total = 0;
1976 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1978 // calculate team size total (sum of all team sizes)
1979 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1980 if(tm.team != NUM_SPECTATOR)
1981 team_size_total += tm.team_size;
1984 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1986 if(tm.team == NUM_SPECTATOR)
1991 draw_beginBoldFont();
1992 vector rgb = Team_ColorRGB(tm.team);
1993 str = ftos(tm.(teamscores(ts_primary)));
1994 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1996 // team score on the left (default)
1997 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2001 // team score on the right
2002 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2004 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2006 // team size (if set to show on the side)
2007 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2009 // calculate the starting position for the whole team size info string
2010 str = sprintf("%d/%d", tm.team_size, team_size_total);
2011 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2013 // team size on the left
2014 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2018 // team size on the right
2019 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2021 str = sprintf("%d", tm.team_size);
2022 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2023 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2024 str = sprintf("/%d", team_size_total);
2025 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2029 // secondary score, e.g. keyhunt
2030 if(ts_primary != ts_secondary)
2032 str = ftos(tm.(teamscores(ts_secondary)));
2033 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2036 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2041 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2044 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2047 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2048 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2049 else if(panel_bg_color_team > 0)
2050 panel_bg_color = rgb * panel_bg_color_team;
2052 panel_bg_color = rgb;
2053 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2055 panel_bg_color = panel_bg_color_save;
2059 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2060 if(tm.team != NUM_SPECTATOR)
2063 // display it anyway
2064 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2067 // draw scoreboard spectators before accuracy and item stats
2068 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2069 pos = Scoreboard_Spectators_Draw(pos);
2072 // draw accuracy and item stats
2073 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2074 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2075 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2076 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2078 // draw scoreboard spectators after accuracy and item stats and before rankings
2079 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2080 pos = Scoreboard_Spectators_Draw(pos);
2083 if(MUTATOR_CALLHOOK(ShowRankings)) {
2084 string ranktitle = M_ARGV(0, string);
2085 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2086 if(race_speedaward) {
2087 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2088 pos.y += 1.25 * hud_fontsize.y;
2090 if(race_speedaward_alltimebest) {
2091 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);
2092 pos.y += 1.25 * hud_fontsize.y;
2094 if (race_speedaward || race_speedaward_alltimebest)
2095 pos.y += 0.25 * hud_fontsize.y;
2096 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2101 // draw scoreboard spectators after rankings
2102 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2103 pos = Scoreboard_Spectators_Draw(pos);
2106 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2108 // draw scoreboard spectators after mapstats
2109 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2110 pos = Scoreboard_Spectators_Draw(pos);
2114 // print information about respawn status
2115 float respawn_time = STAT(RESPAWN_TIME);
2116 if(!intermission && respawn_time)
2118 if(respawn_time < 0)
2120 // a negative number means we are awaiting respawn, time value is still the same
2121 respawn_time *= -1; // remove mark now that we checked it
2123 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2124 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2126 str = sprintf(_("^1Respawning in ^3%s^1..."),
2127 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2128 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2130 count_seconds(ceil(respawn_time - time))
2134 else if(time < respawn_time)
2136 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2137 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2138 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2140 count_seconds(ceil(respawn_time - time))
2144 else if(time >= respawn_time)
2145 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2147 pos.y += 1.2 * hud_fontsize.y;
2148 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2151 pos.y += hud_fontsize.y;
2152 if (scoreboard_fade_alpha < 1)
2153 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2154 else if (pos.y != scoreboard_bottom)
2156 if (pos.y > scoreboard_bottom)
2157 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2159 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2164 if (scoreboard_fade_alpha == 1)
2166 if (scoreboard_bottom > 0.95 * vid_conheight)
2167 rankings_rows = max(1, rankings_rows - 1);
2168 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2169 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2171 rankings_cnt = rankings_rows * rankings_columns;