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];
46 float sbt_field_title_condense_factor[MAX_SBT_FIELDS + 1];
47 float sbt_field_title_width[MAX_SBT_FIELDS + 1];
49 float sbt_field_title_maxwidth;
50 float sbt_field_title_maxwidth_factor;
52 string autocvar_hud_fontsize;
57 float sbt_fg_alpha_self;
59 float sbt_highlight_alpha;
60 float sbt_highlight_alpha_self;
61 float sbt_highlight_alpha_eliminated;
63 // provide basic panel cvars to old clients
64 // TODO remove them after a future release (0.8.2+)
65 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
66 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
67 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
68 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
69 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
70 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
71 noref string autocvar_hud_panel_scoreboard_bg_border = "";
72 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
74 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
75 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
76 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
77 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
78 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
79 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
80 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
81 float autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth = 0.07;
82 bool autocvar_hud_panel_scoreboard_table_highlight = true;
83 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
84 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
85 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
86 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
87 float autocvar_hud_panel_scoreboard_team_size_position = 0;
88 float autocvar_hud_panel_scoreboard_spectators_position = 1;
90 bool autocvar_hud_panel_scoreboard_accuracy = true;
91 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
93 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
94 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_itemstats = true;
97 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
98 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
99 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
100 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
101 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
103 bool autocvar_hud_panel_scoreboard_dynamichud = false;
105 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
106 bool autocvar_hud_panel_scoreboard_others_showscore = true;
107 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
108 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
109 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
110 bool autocvar_hud_panel_scoreboard_playerid = false;
111 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
112 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
113 bool autocvar_hud_panel_scoreboard_scores_per_round;
115 float scoreboard_time;
119 if(autocvar_hud_panel_scoreboard_scores_per_round)
120 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
123 // mode 0: returns translated label
124 // mode 1: prints name and description of all the labels
125 string Label_getInfo(string label, int mode)
128 label = "bckills"; // first case in the switch
130 #define SCO_LABEL(strlabel, label, padding, help) \
133 return CTX(strlabel); \
134 LOG_HELP("^3", label, padding, "^7", help);
138 SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
139 SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
140 SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
141 SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
142 SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
143 SCO_LABEL(_("SCO^destroyed"), "destroyed", " ", _("Number of keys destroyed by pushing them into void"));
144 SCO_LABEL(_("SCO^damage"), "dmg", " ", _("The total damage done"));
145 SCO_LABEL(_("SCO^dmgtaken"), "dmgtaken", " ", _("The total damage taken"));
146 SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
147 SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
148 SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
149 SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
150 SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
151 SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
152 SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
153 SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
154 SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
155 SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
156 SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
157 SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
158 SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
159 SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
160 SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
161 SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
162 SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
163 SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
164 SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
165 SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
166 SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
167 SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
168 SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
169 SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
170 SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
171 SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
172 SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
173 SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
174 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
175 SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
176 SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
177 SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
178 SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
179 SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
180 SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
181 SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
182 SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
188 bool scoreboard_ui_disabling;
189 void HUD_Scoreboard_UI_Disable()
191 scoreboard_ui_disabling = true;
192 sb_showscores = false;
195 void HUD_Scoreboard_UI_Disable_Instantly()
197 scoreboard_ui_disabling = false;
198 scoreboard_ui_enabled = 0;
199 scoreboard_selected_panel = 0;
200 scoreboard_selected_player = NULL;
201 scoreboard_selected_team = NULL;
204 // mode: 0 normal, 1 team selection
205 void Scoreboard_UI_Enable(int mode)
211 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
214 // release player's pressed keys as they aren't released elsewhere
215 // in particular jump needs to be released as it may open the team selection
216 // (when server detects jump has been pressed it sends the command to open the team selection)
217 Release_Common_Keys();
218 scoreboard_ui_enabled = 2;
219 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
223 if (scoreboard_ui_enabled == 1)
225 scoreboard_ui_enabled = 1;
226 scoreboard_selected_panel = SB_PANEL_FIRST;
228 scoreboard_selected_player = NULL;
229 scoreboard_selected_team = NULL;
230 scoreboard_selected_panel_time = time;
233 int rankings_start_column;
234 int rankings_rows = 0;
235 int rankings_columns = 0;
236 int rankings_cnt = 0;
237 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
241 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
246 mousepos.x = nPrimary;
247 mousepos.y = nSecondary;
254 // at this point bInputType can only be 0 or 1 (key pressed or released)
255 bool key_pressed = (bInputType == 0);
257 // ESC to exit (TAB-ESC works too)
258 if(nPrimary == K_ESCAPE)
262 HUD_Scoreboard_UI_Disable();
266 // block any input while a menu dialog is fading
267 if(autocvar__menu_alpha)
273 // allow console bind to work
274 string con_keys = findkeysforcommand("toggleconsole", 0);
275 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
277 bool hit_con_bind = false;
279 for (i = 0; i < keys; ++i)
281 if(nPrimary == stof(argv(i)))
286 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
287 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
288 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
289 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
292 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
293 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
294 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
295 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
298 if(nPrimary == K_TAB)
302 if (scoreboard_ui_enabled == 2)
304 if (hudShiftState & S_SHIFT)
307 goto downarrow_action;
310 if (hudShiftState & S_SHIFT)
312 --scoreboard_selected_panel;
313 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
314 --scoreboard_selected_panel;
315 if (scoreboard_selected_panel < SB_PANEL_FIRST)
316 scoreboard_selected_panel = SB_PANEL_MAX;
320 ++scoreboard_selected_panel;
321 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
322 ++scoreboard_selected_panel;
323 if (scoreboard_selected_panel > SB_PANEL_MAX)
324 scoreboard_selected_panel = SB_PANEL_FIRST;
327 scoreboard_selected_panel_time = time;
329 else if(nPrimary == K_DOWNARROW)
333 LABEL(downarrow_action);
334 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
336 if (scoreboard_ui_enabled == 2)
338 entity curr_team = NULL;
339 bool scoreboard_selected_team_found = false;
340 if (!scoreboard_selected_team)
341 scoreboard_selected_team_found = true;
343 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
345 if(tm.team == NUM_SPECTATOR)
348 if (scoreboard_selected_team_found)
350 if (scoreboard_selected_team == tm)
351 scoreboard_selected_team_found = true;
354 if (curr_team == scoreboard_selected_team) // loop reached the last team
356 scoreboard_selected_team = curr_team;
361 entity curr_pl = NULL;
362 bool scoreboard_selected_player_found = false;
363 if (!scoreboard_selected_player)
364 scoreboard_selected_player_found = true;
366 for(tm = teams.sort_next; tm; tm = tm.sort_next)
368 if(tm.team != NUM_SPECTATOR)
369 for(pl = players.sort_next; pl; pl = pl.sort_next)
371 if(pl.team != tm.team)
374 if (scoreboard_selected_player_found)
376 if (scoreboard_selected_player == pl)
377 scoreboard_selected_player_found = true;
381 if (curr_pl == scoreboard_selected_player) // loop reached the last player
383 scoreboard_selected_player = curr_pl;
387 else if(nPrimary == K_UPARROW)
391 LABEL(uparrow_action);
392 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
394 if (scoreboard_ui_enabled == 2)
396 entity prev_team = NULL;
397 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
399 if(tm.team == NUM_SPECTATOR)
401 if (tm == scoreboard_selected_team)
406 scoreboard_selected_team = prev_team;
410 entity prev_pl = NULL;
412 for(tm = teams.sort_next; tm; tm = tm.sort_next)
414 if(tm.team != NUM_SPECTATOR)
415 for(pl = players.sort_next; pl; pl = pl.sort_next)
417 if(pl.team != tm.team)
419 if (pl == scoreboard_selected_player)
425 scoreboard_selected_player = prev_pl;
429 else if(nPrimary == K_RIGHTARROW)
433 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
434 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
436 else if(nPrimary == K_LEFTARROW)
440 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
441 rankings_start_column = max(rankings_start_column - 1, 0);
443 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
447 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
449 if (scoreboard_ui_enabled == 2)
452 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
455 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
456 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
457 HUD_Scoreboard_UI_Disable();
459 else if (scoreboard_selected_player)
460 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
463 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
467 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
469 switch (scoreboard_selected_columns_layout)
472 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
474 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
475 scoreboard_selected_columns_layout = 1;
480 localcmd(sprintf("scoreboard_columns_set default\n"));
481 scoreboard_selected_columns_layout = 2;
484 localcmd(sprintf("scoreboard_columns_set all\n"));
485 scoreboard_selected_columns_layout = 0;
490 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
494 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
495 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
497 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
501 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
503 if (scoreboard_selected_player)
505 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
506 HUD_Scoreboard_UI_Disable();
510 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
514 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
516 if (scoreboard_selected_player)
517 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
520 else if(hit_con_bind || nPrimary == K_PAUSE)
526 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
527 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
529 void Scoreboard_InitScores()
533 ps_primary = ps_secondary = NULL;
534 ts_primary = ts_secondary = -1;
535 FOREACH(Scores, true, {
536 if(scores_flags(it) & SFL_NOT_SORTABLE)
538 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
539 if(f == SFL_SORT_PRIO_PRIMARY)
541 if(f == SFL_SORT_PRIO_SECONDARY)
544 if(ps_secondary == NULL)
545 ps_secondary = ps_primary;
547 for(i = 0; i < MAX_TEAMSCORE; ++i)
549 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
550 if(f == SFL_SORT_PRIO_PRIMARY)
552 if(f == SFL_SORT_PRIO_SECONDARY)
555 if(ts_secondary == -1)
556 ts_secondary = ts_primary;
558 Cmd_Scoreboard_SetFields(0);
562 void Scoreboard_UpdatePlayerTeams()
564 static float update_time;
565 if (time <= update_time)
572 for(pl = players.sort_next; pl; pl = pl.sort_next)
574 numplayers += pl.team != NUM_SPECTATOR;
576 int Team = entcs_GetScoreTeam(pl.sv_entnum);
577 if(SetTeam(pl, Team))
580 Scoreboard_UpdatePlayerPos(pl);
584 pl = players.sort_next;
589 print(strcat("PNUM: ", ftos(num), "\n"));
594 int Scoreboard_CompareScore(int vl, int vr, int f)
596 TC(int, vl); TC(int, vr); TC(int, f);
597 if(f & SFL_ZERO_IS_WORST)
599 if(vl == 0 && vr != 0)
601 if(vl != 0 && vr == 0)
605 return IS_INCREASING(f);
607 return IS_DECREASING(f);
611 float Scoreboard_ComparePlayerScores(entity left, entity right)
613 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
614 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
621 if(vl == NUM_SPECTATOR)
623 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
625 if(!left.gotscores && right.gotscores)
630 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
631 if (res >= 0) return res;
633 if (ps_secondary && ps_secondary != ps_primary)
635 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
636 if (res >= 0) return res;
639 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
640 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
641 if (res >= 0) return res;
644 if (left.sv_entnum < right.sv_entnum)
650 void Scoreboard_UpdatePlayerPos(entity player)
653 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
655 SORT_SWAP(player, ent);
657 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
659 SORT_SWAP(ent, player);
663 float Scoreboard_CompareTeamScores(entity left, entity right)
665 if(left.team == NUM_SPECTATOR)
667 if(right.team == NUM_SPECTATOR)
672 for(int i = -2; i < MAX_TEAMSCORE; ++i)
676 if (fld_idx == -1) fld_idx = ts_primary;
677 else if (ts_secondary == ts_primary) continue;
678 else fld_idx = ts_secondary;
683 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
686 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
687 if (r >= 0) return r;
690 if (left.team < right.team)
696 void Scoreboard_UpdateTeamPos(entity Team)
699 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
701 SORT_SWAP(Team, ent);
703 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
705 SORT_SWAP(ent, Team);
709 void Cmd_Scoreboard_Help()
711 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
712 LOG_HELP(_("Usage:"));
713 LOG_HELP("^2scoreboard_columns_set ^3default");
714 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
715 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
716 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
717 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
718 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
719 LOG_HELP(_("The following field names are recognized (case insensitive):"));
725 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
726 "of game types, then a slash, to make the field show up only in these\n"
727 "or in all but these game types. You can also specify 'all' as a\n"
728 "field to show all fields available for the current game mode."));
731 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
732 "include/exclude ALL teams/noteams game modes."));
735 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
736 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
737 "right of the vertical bar aligned to the right."));
738 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
739 "other gamemodes except DM."));
742 // NOTE: adding a gametype with ? to not warn for an optional field
743 // make sure it's excluded in a previous exclusive rule, if any
744 // otherwise the previous exclusive rule warns anyway
745 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
746 #define SCOREBOARD_DEFAULT_COLUMNS \
747 "ping pl fps name |" \
748 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
749 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
751 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
752 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
753 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
754 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
755 " +surv/survivals +surv/hunts" \
756 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
757 " +lms/lives +lms/rank" \
758 " +kh/kckills +kh/losses +kh/caps" \
759 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
760 " +as/objectives +nb/faults +nb/goals" \
761 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
762 " +dom/ticks +dom/takes" \
763 " -lms,rc,cts,inv,nb/score"
765 void Cmd_Scoreboard_SetFields(int argc)
770 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
774 return; // do nothing, we don't know gametype and scores yet
776 // sbt_fields uses strunzone on the titles!
777 if(!sbt_field_title[0])
778 for(i = 0; i < MAX_SBT_FIELDS; ++i)
779 sbt_field_title[i] = strzone("(null)");
781 // TODO: re enable with gametype dependant cvars?
782 if(argc < 3) // no arguments provided
783 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
786 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
790 if(argv(2) == "default" || argv(2) == "expand_default")
792 if(argv(2) == "expand_default")
793 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
794 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
796 else if(argv(2) == "all" || argv(2) == "ALL")
798 string s = "ping pl name |"; // scores without label (not really scores)
801 // scores without label
802 s = strcat(s, " ", "sum");
803 s = strcat(s, " ", "kdratio");
804 s = strcat(s, " ", "frags");
806 FOREACH(Scores, true, {
808 if(it != ps_secondary)
809 if(scores_label(it) != "")
810 s = strcat(s, " ", scores_label(it));
812 if(ps_secondary != ps_primary)
813 s = strcat(s, " ", scores_label(ps_secondary));
814 s = strcat(s, " ", scores_label(ps_primary));
815 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
822 hud_fontsize = HUD_GetFontsize("hud_fontsize");
824 for(i = 1; i < argc - 1; ++i)
827 bool nocomplain = false;
828 if(substring(str, 0, 1) == "?")
831 str = substring(str, 1, strlen(str) - 1);
834 slash = strstrofs(str, "/", 0);
837 pattern = substring(str, 0, slash);
838 str = substring(str, slash + 1, strlen(str) - (slash + 1));
840 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
844 str = strtolower(str);
845 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
850 // fields without a label (not networked via the score system)
851 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
852 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
853 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
854 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
855 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
856 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
857 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
858 default: // fields with a label
860 // map alternative labels
861 if (str == "damage") str = "dmg";
862 if (str == "damagetaken") str = "dmgtaken";
864 FOREACH(Scores, true, {
865 if (str == strtolower(scores_label(it))) {
867 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
871 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
872 if(!nocomplain && str != "fps") // server can disable the fps field
873 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
875 strfree(sbt_field_title[sbt_num_fields]);
879 sbt_field[sbt_num_fields] = j;
882 if(j == ps_secondary)
883 have_secondary = true;
888 if(sbt_num_fields >= MAX_SBT_FIELDS)
892 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
894 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
895 have_secondary = true;
896 if(ps_primary == ps_secondary)
897 have_secondary = true;
898 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
900 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
904 strfree(sbt_field_title[sbt_num_fields]);
905 for(i = sbt_num_fields; i > 0; --i)
907 sbt_field_title[i] = sbt_field_title[i-1];
908 sbt_field[i] = sbt_field[i-1];
910 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
911 sbt_field[0] = SP_NAME;
913 LOG_INFO("fixed missing field 'name'");
917 strfree(sbt_field_title[sbt_num_fields]);
918 for(i = sbt_num_fields; i > 1; --i)
920 sbt_field_title[i] = sbt_field_title[i-1];
921 sbt_field[i] = sbt_field[i-1];
923 sbt_field_title[1] = strzone("|");
924 sbt_field[1] = SP_SEPARATOR;
926 LOG_INFO("fixed missing field '|'");
929 else if(!have_separator)
931 strcpy(sbt_field_title[sbt_num_fields], "|");
932 sbt_field[sbt_num_fields] = SP_SEPARATOR;
934 LOG_INFO("fixed missing field '|'");
938 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
939 sbt_field[sbt_num_fields] = ps_secondary;
941 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
945 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
946 sbt_field[sbt_num_fields] = ps_primary;
948 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
952 sbt_field[sbt_num_fields] = SP_END;
953 sbt_field_size[0] = 0; // tells Scoreboard_Draw to initialize all field sizes
956 string Scoreboard_AddPlayerId(string pl_name, entity pl)
958 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
959 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
960 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
964 vector sbt_field_rgb;
965 string sbt_field_icon0;
966 string sbt_field_icon1;
967 string sbt_field_icon2;
968 vector sbt_field_icon0_rgb;
969 vector sbt_field_icon1_rgb;
970 vector sbt_field_icon2_rgb;
971 string Scoreboard_GetName(entity pl)
973 if(ready_waiting && pl.ready)
975 sbt_field_icon0 = "gfx/scoreboard/player_ready";
980 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
981 if (playerslots[pl.sv_entnum].colormap >= 1024)
982 f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
984 f = entcs_GetClientColors(pl.sv_entnum);
987 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
988 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
989 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
990 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
991 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
994 return entcs_GetName(pl.sv_entnum);
997 int autocvar_hud_panel_scoreboard_ping_best = 0;
998 int autocvar_hud_panel_scoreboard_ping_medium = 70;
999 int autocvar_hud_panel_scoreboard_ping_high = 100;
1000 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1001 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1002 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1003 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1004 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1005 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1006 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1007 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1008 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1009 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1010 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1011 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1012 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1013 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1015 float tmp, num, denom;
1018 sbt_field_rgb = '1 1 1';
1019 sbt_field_icon0 = "";
1020 sbt_field_icon1 = "";
1021 sbt_field_icon2 = "";
1022 sbt_field_icon0_rgb = '1 1 1';
1023 sbt_field_icon1_rgb = '1 1 1';
1024 sbt_field_icon2_rgb = '1 1 1';
1025 int rounds_played = 0;
1027 rounds_played = pl.(scores(SP_ROUNDS_PL));
1032 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1033 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1038 sbt_field_rgb = COLOR_BEST;
1039 else if(f < PING_MED)
1040 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1041 else if(f < PING_HIGH)
1042 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1043 else if(f < PING_WORST)
1044 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1046 sbt_field_rgb = COLOR_WORST;
1052 f = pl.ping_packetloss;
1053 tmp = pl.ping_movementloss;
1054 if(f == 0 && tmp == 0)
1056 str = ftos(ceil(f * 100));
1058 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1059 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1060 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1064 str = Scoreboard_GetName(pl);
1065 if (autocvar_hud_panel_scoreboard_playerid)
1066 str = Scoreboard_AddPlayerId(str, pl);
1070 f = pl.(scores(SP_KILLS));
1071 f -= pl.(scores(SP_SUICIDES));
1073 return sprintf("%.1f", f / rounds_played);
1077 num = pl.(scores(SP_KILLS));
1078 denom = pl.(scores(SP_DEATHS));
1081 sbt_field_rgb = '0 1 0';
1083 str = sprintf("%.1f", num / rounds_played);
1085 str = sprintf("%d", num);
1086 } else if(num <= 0) {
1087 sbt_field_rgb = '1 0 0';
1089 str = sprintf("%.2f", num / (denom * rounds_played));
1091 str = sprintf("%.1f", num / denom);
1095 str = sprintf("%.2f", num / (denom * rounds_played));
1097 str = sprintf("%.1f", num / denom);
1102 f = pl.(scores(SP_KILLS));
1103 f -= pl.(scores(SP_DEATHS));
1106 sbt_field_rgb = '0 1 0';
1108 sbt_field_rgb = '1 1 1';
1110 sbt_field_rgb = '1 0 0';
1113 return sprintf("%.1f", f / rounds_played);
1118 float elo = pl.(scores(SP_ELO));
1120 case -1: return "...";
1121 case -2: return _("N/A");
1122 default: return ftos(elo);
1128 float fps = pl.(scores(SP_FPS));
1131 sbt_field_rgb = '1 1 1';
1132 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1134 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1135 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1140 return ftos(pl.(scores(field)));
1142 case SP_DMG: case SP_DMGTAKEN:
1144 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1145 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1147 default: case SP_SCORE:
1148 tmp = pl.(scores(field));
1149 f = scores_flags(field);
1150 if(field == ps_primary)
1151 sbt_field_rgb = '1 1 0';
1152 else if(field == ps_secondary)
1153 sbt_field_rgb = '0 1 1';
1155 sbt_field_rgb = '1 1 1';
1156 return ScoreString(f, tmp, rounds_played);
1161 float sbt_fixcolumnwidth_len;
1162 float sbt_fixcolumnwidth_iconlen;
1163 float sbt_fixcolumnwidth_marginlen;
1165 string Scoreboard_FixColumnWidth(int i, string str, bool init)
1171 sbt_fixcolumnwidth_iconlen = 0;
1173 if(sbt_field_icon0 != "")
1175 sz = draw_getimagesize(sbt_field_icon0);
1177 if(sbt_fixcolumnwidth_iconlen < f)
1178 sbt_fixcolumnwidth_iconlen = f;
1181 if(sbt_field_icon1 != "")
1183 sz = draw_getimagesize(sbt_field_icon1);
1185 if(sbt_fixcolumnwidth_iconlen < f)
1186 sbt_fixcolumnwidth_iconlen = f;
1189 if(sbt_field_icon2 != "")
1191 sz = draw_getimagesize(sbt_field_icon2);
1193 if(sbt_fixcolumnwidth_iconlen < f)
1194 sbt_fixcolumnwidth_iconlen = f;
1197 if(sbt_fixcolumnwidth_iconlen != 0)
1199 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1200 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1203 sbt_fixcolumnwidth_marginlen = 0;
1206 sbt_field_title_width[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1208 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1211 float remaining_space = 0;
1212 for(j = 0; j < sbt_num_fields; ++j)
1214 if (sbt_field[i] != SP_SEPARATOR)
1215 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1216 sbt_field_size[i] = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1218 if (sbt_fixcolumnwidth_iconlen != 0)
1219 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1220 float namesize = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1221 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1222 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1224 max_namesize = max(sbt_field_title_width[i], vid_conwidth - remaining_space);
1230 sbt_field_size[i] = sbt_field_title_width[i];
1231 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1232 if (sbt_field_size[i] && sbt_field_size[i] > title_maxwidth)
1233 sbt_field_size[i] = title_maxwidth;
1235 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1238 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1239 if(sbt_field_size[i] < f)
1240 sbt_field_size[i] = f;
1242 sbt_field_title_condense_factor[i] = 0;
1243 if (sbt_field_title_width[i] > sbt_field_size[i])
1245 float real_maxwidth = sbt_field_size[i];
1246 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1247 if (sbt_field_title_width[i] > title_maxwidth)
1248 real_maxwidth = max(sbt_field_size[i], title_maxwidth);
1249 sbt_field_title_condense_factor[i] = real_maxwidth / sbt_field_title_width[i];
1255 void Scoreboard_initFieldSizes(bool compress_more)
1259 sbt_field_title_maxwidth_factor -= 0.05;
1260 if (sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor < 0.01 * vid_conwidth)
1262 sbt_field_title_maxwidth_factor = (0.01 * vid_conwidth) / sbt_field_title_maxwidth;
1267 sbt_field_title_maxwidth_factor = 1;
1269 for(int i = 0; i < sbt_num_fields; ++i)
1271 if (sbt_field[i] == SP_NAME)
1277 Scoreboard_FixColumnWidth(i, "", true);
1280 // update name field size in the end as it takes remaining space
1281 Scoreboard_FixColumnWidth(name_index, "", true);
1284 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1287 vector column_dim = eY * panel_size.y;
1289 column_dim.y -= 1.25 * hud_fontsize.y;
1290 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1291 pos.x += hud_fontsize.x * 0.5;
1292 for(i = 0; i < sbt_num_fields; ++i)
1294 if(sbt_field[i] == SP_SEPARATOR)
1296 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1299 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1300 vector prev_drawfontscale = drawfontscale;
1301 if (sbt_field_title_condense_factor[i])
1302 drawfontscale.x *= sbt_field_title_condense_factor[i];
1303 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1304 if (sbt_field_title_condense_factor[i])
1306 drawfontscale.x *= sbt_field_title_condense_factor[i];
1307 drawfontscale = prev_drawfontscale;
1309 pos.x += column_dim.x;
1312 float left_columns_end = pos.x - hud_fontsize.x;
1314 if(sbt_field[i] == SP_SEPARATOR)
1316 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1317 for(i = sbt_num_fields - 1; i > 0; --i)
1319 if(sbt_field[i] == SP_SEPARATOR)
1322 pos.x -= sbt_field_size[i];
1327 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1328 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1331 vector prev_drawfontscale = drawfontscale;
1332 float titlewidth = stringwidth(sbt_field_title[i], false, hud_fontsize);
1333 if (sbt_field_title_condense_factor[i])
1335 drawfontscale.x *= sbt_field_title_condense_factor[i];
1336 text_offset.x = sbt_field_size[i] - titlewidth * sbt_field_title_condense_factor[i];
1339 text_offset.x = sbt_field_size[i] - titlewidth;
1340 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1341 if (sbt_field_title_condense_factor[i])
1343 drawfontscale.x *= sbt_field_title_condense_factor[i];
1344 drawfontscale = prev_drawfontscale;
1346 pos.x -= hud_fontsize.x;
1349 float right_columns_start = pos.x + hud_fontsize.x * 0.5;
1350 if (left_columns_end > right_columns_start)
1351 Scoreboard_initFieldSizes(true);
1354 pos.x = panel_pos.x;
1355 pos.y += 1.25 * hud_fontsize.y;
1359 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1361 TC(bool, is_self); TC(int, pl_number);
1363 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1365 vector h_pos = item_pos;
1366 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1367 // alternated rows highlighting
1368 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1370 if (pl == scoreboard_selected_player)
1371 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1374 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1375 else if((sbt_highlight) && (!(pl_number % 2)))
1376 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1378 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1380 vector pos = item_pos;
1381 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1383 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1385 pos.x += hud_fontsize.x * 0.5;
1386 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1387 vector tmp = '0 0 0';
1389 PlayerScoreField field;
1390 for(i = 0; i < sbt_num_fields; ++i)
1392 field = sbt_field[i];
1393 if(field == SP_SEPARATOR)
1396 if(is_spec && field != SP_NAME && field != SP_PING) {
1397 pos.x += sbt_field_size[i] + hud_fontsize.x;
1400 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1401 str = Scoreboard_FixColumnWidth(i, str, false);
1403 pos.x += sbt_field_size[i] + hud_fontsize.x;
1405 if(field == SP_NAME) {
1406 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1407 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1409 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1410 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1413 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1414 if(sbt_field_icon0 != "")
1415 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1416 if(sbt_field_icon1 != "")
1417 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1418 if(sbt_field_icon2 != "")
1419 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1422 if(sbt_field[i] == SP_SEPARATOR)
1424 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1425 for(i = sbt_num_fields-1; i > 0; --i)
1427 field = sbt_field[i];
1428 if(field == SP_SEPARATOR)
1431 if(is_spec && field != SP_NAME && field != SP_PING) {
1432 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1436 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1437 str = Scoreboard_FixColumnWidth(i, str, false);
1439 if(field == SP_NAME) {
1440 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1441 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1443 tmp.x = sbt_fixcolumnwidth_len;
1444 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1447 tmp.x = sbt_field_size[i];
1448 if(sbt_field_icon0 != "")
1449 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1450 if(sbt_field_icon1 != "")
1451 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1452 if(sbt_field_icon2 != "")
1453 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1454 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1459 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1462 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1465 vector h_pos = item_pos;
1466 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1468 bool complete = (this_team == NUM_SPECTATOR);
1471 if((sbt_highlight) && (!(pl_number % 2)))
1472 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1474 vector pos = item_pos;
1475 pos.x += hud_fontsize.x * 0.5;
1476 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1478 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1480 width_limit -= stringwidth("...", false, hud_fontsize);
1481 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1482 static float max_name_width = 0;
1484 float fieldsize = 0;
1485 float min_fieldsize = 0;
1486 float fieldpadding = hud_fontsize.x * 0.25;
1487 if(this_team == NUM_SPECTATOR)
1489 if(autocvar_hud_panel_scoreboard_spectators_showping)
1490 min_fieldsize = stringwidth("999", false, hud_fontsize);
1492 else if(autocvar_hud_panel_scoreboard_others_showscore)
1493 min_fieldsize = stringwidth("99", false, hud_fontsize);
1494 for(i = 0; pl; pl = pl.sort_next)
1496 if(pl.team != this_team)
1498 if(pl == ignored_pl)
1502 if(this_team == NUM_SPECTATOR)
1504 if(autocvar_hud_panel_scoreboard_spectators_showping)
1505 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1507 else if(autocvar_hud_panel_scoreboard_others_showscore)
1508 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1510 string str = entcs_GetName(pl.sv_entnum);
1511 if (autocvar_hud_panel_scoreboard_playerid)
1512 str = Scoreboard_AddPlayerId(str, pl);
1513 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1514 float column_width = stringwidth(str, true, hud_fontsize);
1515 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1517 if(column_width > max_name_width)
1518 max_name_width = column_width;
1519 column_width = max_name_width;
1523 fieldsize = stringwidth(field, false, hud_fontsize);
1524 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1527 if(pos.x + column_width > width_limit)
1532 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1537 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1538 pos.y += hud_fontsize.y * 1.25;
1542 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1544 if (pl == scoreboard_selected_player)
1546 h_size.x = column_width + hud_fontsize.x * 0.25;
1547 h_size.y = hud_fontsize.y;
1548 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1552 vector name_pos = pos;
1553 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1554 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1555 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1558 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1559 h_size.y = hud_fontsize.y;
1560 vector field_pos = pos;
1561 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1562 field_pos.x += column_width - h_size.x;
1564 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1565 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1566 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1570 h_size.x = column_width + hud_fontsize.x * 0.25;
1571 h_size.y = hud_fontsize.y;
1572 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1574 pos.x += column_width;
1575 pos.x += hud_fontsize.x;
1577 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1580 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1582 int max_players = 999;
1583 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1585 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1588 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1589 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1590 height /= team_count;
1593 height -= panel_bg_padding * 2; // - padding
1594 max_players = floor(height / (hud_fontsize.y * 1.25));
1595 if(max_players <= 1)
1597 if(max_players == tm.team_size)
1602 entity me = playerslots[current_player];
1604 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1605 panel_size.y += panel_bg_padding * 2;
1607 vector scoreboard_selected_hl_pos = pos;
1608 vector scoreboard_selected_hl_size = '0 0 0';
1609 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1610 scoreboard_selected_hl_size.y = panel_size.y;
1614 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1615 if(panel.current_panel_bg != "0")
1616 end_pos.y += panel_bg_border * 2;
1618 if(panel_bg_padding)
1620 panel_pos += '1 1 0' * panel_bg_padding;
1621 panel_size -= '2 2 0' * panel_bg_padding;
1625 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1629 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1631 pos.y += 1.25 * hud_fontsize.y;
1634 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1636 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1639 // print header row and highlight columns
1640 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1642 // fill the table and draw the rows
1643 bool is_self = false;
1644 bool self_shown = false;
1646 for(pl = players.sort_next; pl; pl = pl.sort_next)
1648 if(pl.team != tm.team)
1650 if(i == max_players - 2 && pl != me)
1652 if(!self_shown && me.team == tm.team)
1654 Scoreboard_DrawItem(pos, rgb, me, true, i);
1656 pos.y += 1.25 * hud_fontsize.y;
1660 if(i >= max_players - 1)
1662 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1665 is_self = (pl.sv_entnum == current_player);
1666 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1669 pos.y += 1.25 * hud_fontsize.y;
1673 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1675 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1677 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1678 _alpha *= panel_fg_alpha;
1680 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1684 panel_size.x += panel_bg_padding * 2; // restore initial width
1688 bool Scoreboard_WouldDraw()
1690 if (scoreboard_ui_enabled)
1692 if (scoreboard_ui_disabling)
1694 if (scoreboard_fade_alpha == 0)
1695 HUD_Scoreboard_UI_Disable_Instantly();
1698 if (intermission && scoreboard_ui_enabled == 2)
1700 HUD_Scoreboard_UI_Disable_Instantly();
1705 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1707 else if (QuickMenu_IsOpened())
1709 else if (HUD_Radar_Clickable())
1711 else if (sb_showscores) // set by +showscores engine command
1713 else if (intermission == 1)
1715 else if (intermission == 2)
1717 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1718 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1722 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1727 float average_accuracy;
1728 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1730 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1732 WepSet weapons_stat = WepSet_GetFromStat();
1733 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1734 int disownedcnt = 0;
1736 FOREACH(Weapons, it != WEP_Null, {
1737 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1739 WepSet set = it.m_wepset;
1740 if(it.spawnflags & WEP_TYPE_OTHER)
1745 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1747 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1754 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1755 if (weapon_cnt <= 0) return pos;
1758 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1760 int columns = ceil(weapon_cnt / rows);
1762 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1763 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1764 float height = weapon_height + hud_fontsize.y;
1766 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);
1767 pos.y += 1.25 * hud_fontsize.y;
1768 if(panel.current_panel_bg != "0")
1769 pos.y += panel_bg_border;
1772 panel_size.y = height * rows;
1773 panel_size.y += panel_bg_padding * 2;
1775 float panel_bg_alpha_save = panel_bg_alpha;
1776 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1778 panel_bg_alpha = panel_bg_alpha_save;
1780 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1781 if(panel.current_panel_bg != "0")
1782 end_pos.y += panel_bg_border * 2;
1784 if(panel_bg_padding)
1786 panel_pos += '1 1 0' * panel_bg_padding;
1787 panel_size -= '2 2 0' * panel_bg_padding;
1791 vector tmp = panel_size;
1793 float weapon_width = tmp.x / columns / rows;
1796 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1800 // column highlighting
1801 for (int i = 0; i < columns; ++i)
1803 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);
1806 for (int i = 0; i < rows; ++i)
1807 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1810 average_accuracy = 0;
1811 int weapons_with_stats = 0;
1813 pos.x += weapon_width / 2;
1815 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1818 Accuracy_LoadColors();
1820 float oldposx = pos.x;
1824 FOREACH(Weapons, it != WEP_Null, {
1825 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1827 WepSet set = it.m_wepset;
1828 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1830 if (it.spawnflags & WEP_TYPE_OTHER)
1834 if (weapon_stats >= 0)
1835 weapon_alpha = sbt_fg_alpha;
1837 weapon_alpha = 0.2 * sbt_fg_alpha;
1840 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1842 if (weapon_stats >= 0) {
1843 weapons_with_stats += 1;
1844 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1846 string s = sprintf("%d%%", weapon_stats * 100);
1847 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1849 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1850 rgb = Accuracy_GetColor(weapon_stats);
1852 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1854 tmpos.x += weapon_width * rows;
1855 pos.x += weapon_width * rows;
1856 if (rows == 2 && column == columns - 1) {
1864 if (weapons_with_stats)
1865 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1867 panel_size.x += panel_bg_padding * 2; // restore initial width
1872 bool is_item_filtered(entity it)
1874 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1876 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1879 if (it.instanceOfArmor || it.instanceOfHealth)
1881 int ha_mask = floor(mask) % 10;
1884 default: return false;
1885 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1886 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1887 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1888 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1891 if (it.instanceOfAmmo)
1893 int ammo_mask = floor(mask / 10) % 10;
1894 return (ammo_mask == 1);
1899 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1901 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1903 int disowned_cnt = 0;
1904 int uninteresting_cnt = 0;
1905 IL_EACH(default_order_items, true, {
1906 int q = g_inventory.inv_items[it.m_id];
1907 //q = 1; // debug: display all items
1908 if (is_item_filtered(it))
1909 ++uninteresting_cnt;
1913 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1914 int n = items_cnt - disowned_cnt;
1915 if (n <= 0) return pos;
1917 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1918 int columns = max(6, ceil(n / rows));
1920 float item_height = hud_fontsize.y * 2.3;
1921 float height = item_height + hud_fontsize.y;
1923 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1924 pos.y += 1.25 * hud_fontsize.y;
1925 if(panel.current_panel_bg != "0")
1926 pos.y += panel_bg_border;
1929 panel_size.y = height * rows;
1930 panel_size.y += panel_bg_padding * 2;
1932 float panel_bg_alpha_save = panel_bg_alpha;
1933 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1935 panel_bg_alpha = panel_bg_alpha_save;
1937 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1938 if(panel.current_panel_bg != "0")
1939 end_pos.y += panel_bg_border * 2;
1941 if(panel_bg_padding)
1943 panel_pos += '1 1 0' * panel_bg_padding;
1944 panel_size -= '2 2 0' * panel_bg_padding;
1948 vector tmp = panel_size;
1950 float item_width = tmp.x / columns / rows;
1953 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1957 // column highlighting
1958 for (int i = 0; i < columns; ++i)
1960 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);
1963 for (int i = 0; i < rows; ++i)
1964 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1968 pos.x += item_width / 2;
1970 float oldposx = pos.x;
1974 IL_EACH(default_order_items, !is_item_filtered(it), {
1975 int n = g_inventory.inv_items[it.m_id];
1976 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1977 if (n <= 0) continue;
1978 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);
1980 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1981 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1982 tmpos.x += item_width * rows;
1983 pos.x += item_width * rows;
1984 if (rows == 2 && column == columns - 1) {
1992 panel_size.x += panel_bg_padding * 2; // restore initial width
1997 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1999 pos.x += hud_fontsize.x * 0.25;
2000 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2001 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
2002 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2004 pos.y += hud_fontsize.y;
2009 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
2010 float stat_secrets_found, stat_secrets_total;
2011 float stat_monsters_killed, stat_monsters_total;
2015 // get monster stats
2016 stat_monsters_killed = STAT(MONSTERS_KILLED);
2017 stat_monsters_total = STAT(MONSTERS_TOTAL);
2019 // get secrets stats
2020 stat_secrets_found = STAT(SECRETS_FOUND);
2021 stat_secrets_total = STAT(SECRETS_TOTAL);
2023 // get number of rows
2024 if(stat_secrets_total)
2026 if(stat_monsters_total)
2029 // if no rows, return
2033 // draw table header
2034 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2035 pos.y += 1.25 * hud_fontsize.y;
2036 if(panel.current_panel_bg != "0")
2037 pos.y += panel_bg_border;
2040 panel_size.y = hud_fontsize.y * rows;
2041 panel_size.y += panel_bg_padding * 2;
2044 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2045 if(panel.current_panel_bg != "0")
2046 end_pos.y += panel_bg_border * 2;
2048 if(panel_bg_padding)
2050 panel_pos += '1 1 0' * panel_bg_padding;
2051 panel_size -= '2 2 0' * panel_bg_padding;
2055 vector tmp = panel_size;
2058 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2061 if(stat_monsters_total)
2063 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2064 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2068 if(stat_secrets_total)
2070 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2071 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2074 panel_size.x += panel_bg_padding * 2; // restore initial width
2078 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2081 RANKINGS_RECEIVED_CNT = 0;
2082 for (i=RANKINGS_CNT-1; i>=0; --i)
2084 ++RANKINGS_RECEIVED_CNT;
2086 if (RANKINGS_RECEIVED_CNT == 0)
2089 vector hl_rgb = rgb + '0.5 0.5 0.5';
2091 vector scoreboard_selected_hl_pos = pos;
2093 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2094 pos.y += 1.25 * hud_fontsize.y;
2095 if(panel.current_panel_bg != "0")
2096 pos.y += panel_bg_border;
2098 vector scoreboard_selected_hl_size = '0 0 0';
2099 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2100 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2105 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2107 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2112 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2114 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2118 float ranksize = 3 * hud_fontsize.x;
2119 float timesize = 5 * hud_fontsize.x;
2120 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2121 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2122 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2125 rankings_cnt = RANKINGS_RECEIVED_CNT;
2126 rankings_rows = ceil(rankings_cnt / rankings_columns);
2129 // expand name column to fill the entire row
2130 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2131 namesize += available_space;
2132 columnsize.x += available_space;
2134 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2135 panel_size.y += panel_bg_padding * 2;
2136 scoreboard_selected_hl_size.y += panel_size.y;
2140 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2141 if(panel.current_panel_bg != "0")
2142 end_pos.y += panel_bg_border * 2;
2144 if(panel_bg_padding)
2146 panel_pos += '1 1 0' * panel_bg_padding;
2147 panel_size -= '2 2 0' * panel_bg_padding;
2153 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2155 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2157 int column = 0, j = 0;
2158 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2159 int start_item = rankings_start_column * rankings_rows;
2160 for(i = start_item; i < start_item + rankings_cnt; ++i)
2162 int t = grecordtime[i];
2166 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2167 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2168 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2169 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2171 str = count_ordinal(i+1);
2172 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2173 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2174 str = ColorTranslateRGB(grecordholder[i]);
2176 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2177 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2179 pos.y += 1.25 * hud_fontsize.y;
2181 if(j >= rankings_rows)
2185 pos.x += panel_size.x / rankings_columns;
2186 pos.y = panel_pos.y;
2189 strfree(zoned_name_self);
2191 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2193 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2194 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2197 panel_size.x += panel_bg_padding * 2; // restore initial width
2201 bool have_weapon_stats;
2202 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2204 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2206 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2209 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2210 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2216 if (!have_weapon_stats)
2218 FOREACH(Weapons, it != WEP_Null, {
2219 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2220 if (weapon_stats >= 0)
2222 have_weapon_stats = true;
2226 if (!have_weapon_stats)
2233 bool have_item_stats;
2234 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2236 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2238 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2241 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2242 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2248 if (!have_item_stats)
2250 IL_EACH(default_order_items, true, {
2251 if (!is_item_filtered(it))
2253 int q = g_inventory.inv_items[it.m_id];
2254 //q = 1; // debug: display all items
2257 have_item_stats = true;
2262 if (!have_item_stats)
2269 vector Scoreboard_Spectators_Draw(vector pos) {
2274 for(pl = players.sort_next; pl; pl = pl.sort_next)
2276 if(pl.team == NUM_SPECTATOR)
2278 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2279 if(tm.team == NUM_SPECTATOR)
2281 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2282 draw_beginBoldFont();
2283 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2285 pos.y += 1.25 * hud_fontsize.y;
2287 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2288 pos.y += 1.25 * hud_fontsize.y;
2293 if (str != "") // if there's at least one spectator
2294 pos.y += 0.5 * hud_fontsize.y;
2299 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2301 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2302 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2303 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2304 (s_label == "score") ? CTX(_("SCO^points")) :
2305 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2308 void Scoreboard_Draw()
2310 bool sb_init_field_sizes = false;
2312 if(!autocvar__hud_configure)
2314 if(!hud_draw_maximized) return;
2316 // frametime checks allow to toggle the scoreboard even when the game is paused
2317 if(scoreboard_active) {
2318 if (scoreboard_fade_alpha == 0)
2319 scoreboard_time = time;
2320 if(hud_configure_menu_open == 1)
2321 scoreboard_fade_alpha = 1;
2322 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2323 if (scoreboard_fadeinspeed && frametime)
2324 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2326 scoreboard_fade_alpha = 1;
2328 static string hud_fontsize_str;
2329 if(hud_fontsize_str != autocvar_hud_fontsize)
2331 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2332 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2333 sb_init_field_sizes = true;
2336 static float scoreboard_table_fieldtitle_maxwidth_prev;
2337 if (scoreboard_table_fieldtitle_maxwidth_prev != autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth)
2339 scoreboard_table_fieldtitle_maxwidth_prev = autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth;
2340 sbt_field_title_maxwidth = bound(0.01, autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth, 0.1);
2341 sbt_field_title_maxwidth *= vid_conwidth;
2342 sb_init_field_sizes = true;
2346 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2347 if (scoreboard_fadeoutspeed && frametime)
2348 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2350 scoreboard_fade_alpha = 0;
2353 if (!scoreboard_fade_alpha)
2355 scoreboard_acc_fade_alpha = 0;
2356 scoreboard_itemstats_fade_alpha = 0;
2361 scoreboard_fade_alpha = 0;
2363 if (autocvar_hud_panel_scoreboard_dynamichud)
2366 HUD_Scale_Disable();
2368 if(scoreboard_fade_alpha <= 0)
2370 panel_fade_alpha *= scoreboard_fade_alpha;
2371 HUD_Panel_LoadCvars();
2373 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2374 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2375 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2376 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2377 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2378 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2379 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2381 // don't overlap with con_notify
2382 if(!autocvar__hud_configure)
2383 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2385 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2386 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2387 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2388 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2389 panel_pos.x = scoreboard_left;
2390 panel_size.x = fixed_scoreboard_width;
2392 // field sizes can be initialized now after panel_size.x calculation
2393 if (!sbt_field_size[0] || sb_init_field_sizes)
2394 Scoreboard_initFieldSizes(false);
2396 Scoreboard_UpdatePlayerTeams();
2398 scoreboard_top = panel_pos.y;
2399 vector pos = panel_pos;
2404 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2406 // Begin of Game Info Section
2407 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2408 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2410 // Game Info: Game Type
2411 if (scoreboard_ui_enabled == 2)
2412 str = _("Team Selection");
2413 else if (gametype_custom_name != "")
2414 str = gametype_custom_name;
2416 str = MapInfo_Type_ToText(gametype);
2417 draw_beginBoldFont();
2418 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);
2421 pos.y += sb_gameinfo_type_fontsize.y;
2422 // Game Info: Game Detail
2423 if (scoreboard_ui_enabled == 2)
2425 if (scoreboard_selected_team)
2426 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2428 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2429 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2431 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2432 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2433 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2437 float tl = STAT(TIMELIMIT);
2438 float fl = STAT(FRAGLIMIT);
2439 float ll = STAT(LEADLIMIT);
2440 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2443 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2444 if(!gametype.m_hidelimits)
2449 str = strcat(str, "^7 / "); // delimiter
2450 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2454 if(tl > 0 || fl > 0)
2457 if (ll_and_fl && fl > 0)
2458 str = strcat(str, "^7 & ");
2460 str = strcat(str, "^7 / ");
2462 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2465 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
2466 // map name and player count
2470 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2471 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2472 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2474 // End of Game Info Section
2476 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2477 if(panel.current_panel_bg != "0")
2478 pos.y += panel_bg_border;
2480 // Draw the scoreboard
2481 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2484 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2488 vector panel_bg_color_save = panel_bg_color;
2489 vector team_score_baseoffset;
2490 vector team_size_baseoffset;
2491 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2493 // put team score to the left of scoreboard (and team size to the right)
2494 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2495 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2496 if(panel.current_panel_bg != "0")
2498 team_score_baseoffset.x -= panel_bg_border;
2499 team_size_baseoffset.x += panel_bg_border;
2504 // put team score to the right of scoreboard (and team size to the left)
2505 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2506 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2507 if(panel.current_panel_bg != "0")
2509 team_score_baseoffset.x += panel_bg_border;
2510 team_size_baseoffset.x -= panel_bg_border;
2514 int team_size_total = 0;
2515 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2517 // calculate team size total (sum of all team sizes)
2518 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2519 if(tm.team != NUM_SPECTATOR)
2520 team_size_total += tm.team_size;
2523 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2525 if(tm.team == NUM_SPECTATOR)
2530 draw_beginBoldFont();
2531 vector rgb = Team_ColorRGB(tm.team);
2532 str = ftos(tm.(teamscores(ts_primary)));
2533 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2535 // team score on the left (default)
2536 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2540 // team score on the right
2541 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2543 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2545 // team size (if set to show on the side)
2546 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2548 // calculate the starting position for the whole team size info string
2549 str = sprintf("%d/%d", tm.team_size, team_size_total);
2550 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2552 // team size on the left
2553 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2557 // team size on the right
2558 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2560 str = sprintf("%d", tm.team_size);
2561 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2562 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2563 str = sprintf("/%d", team_size_total);
2564 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2568 // secondary score, e.g. keyhunt
2569 if(ts_primary != ts_secondary)
2571 str = ftos(tm.(teamscores(ts_secondary)));
2572 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2575 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2580 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2583 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2586 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2587 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2588 else if(panel_bg_color_team > 0)
2589 panel_bg_color = rgb * panel_bg_color_team;
2591 panel_bg_color = rgb;
2592 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2594 panel_bg_color = panel_bg_color_save;
2598 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2599 if(tm.team != NUM_SPECTATOR)
2602 // display it anyway
2603 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2606 // draw scoreboard spectators before accuracy and item stats
2607 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2608 pos = Scoreboard_Spectators_Draw(pos);
2611 // draw accuracy and item stats
2612 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2613 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2614 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2615 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2617 // draw scoreboard spectators after accuracy and item stats and before rankings
2618 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2619 pos = Scoreboard_Spectators_Draw(pos);
2622 if(MUTATOR_CALLHOOK(ShowRankings)) {
2623 string ranktitle = M_ARGV(0, string);
2624 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2625 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2626 if(race_speedaward_alltimebest)
2629 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2633 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2634 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2635 str = strcat(str, " / ");
2637 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2638 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2639 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2640 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2642 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2647 // draw scoreboard spectators after rankings
2648 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2649 pos = Scoreboard_Spectators_Draw(pos);
2652 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2654 // draw scoreboard spectators after mapstats
2655 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2656 pos = Scoreboard_Spectators_Draw(pos);
2660 // print information about respawn status
2661 float respawn_time = STAT(RESPAWN_TIME);
2662 if(!intermission && respawn_time)
2664 if(respawn_time < 0)
2666 // a negative number means we are awaiting respawn, time value is still the same
2667 respawn_time *= -1; // remove mark now that we checked it
2669 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2670 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2672 str = sprintf(_("^1Respawning in ^3%s^1..."),
2673 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2674 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2676 count_seconds(ceil(respawn_time - time))
2680 else if(time < respawn_time)
2682 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2683 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2684 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2686 count_seconds(ceil(respawn_time - time))
2690 else if(time >= respawn_time)
2691 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2693 pos.y += 1.2 * hud_fontsize.y;
2694 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2697 pos.y += hud_fontsize.y;
2698 if (scoreboard_fade_alpha < 1)
2699 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2700 else if (pos.y != scoreboard_bottom)
2702 if (pos.y > scoreboard_bottom)
2703 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2705 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2710 if (scoreboard_fade_alpha == 1)
2712 if (scoreboard_bottom > 0.95 * vid_conheight)
2713 rankings_rows = max(1, rankings_rows - 1);
2714 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2715 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2717 rankings_cnt = rankings_rows * rankings_columns;