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;
51 string autocvar_hud_fontsize;
56 float sbt_fg_alpha_self;
58 float sbt_highlight_alpha;
59 float sbt_highlight_alpha_self;
60 float sbt_highlight_alpha_eliminated;
62 // provide basic panel cvars to old clients
63 // TODO remove them after a future release (0.8.2+)
64 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
65 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
66 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
67 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
68 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
69 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
70 noref string autocvar_hud_panel_scoreboard_bg_border = "";
71 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
73 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
74 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
75 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
76 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
77 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
78 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
79 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
80 float autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth = 0.07;
81 bool autocvar_hud_panel_scoreboard_table_highlight = true;
82 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
83 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
84 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
85 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
86 float autocvar_hud_panel_scoreboard_team_size_position = 0;
87 float autocvar_hud_panel_scoreboard_spectators_position = 1;
89 bool autocvar_hud_panel_scoreboard_accuracy = true;
90 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
91 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
92 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
93 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
95 bool autocvar_hud_panel_scoreboard_itemstats = true;
96 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
97 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
98 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
99 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
100 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
102 bool autocvar_hud_panel_scoreboard_dynamichud = false;
104 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
105 bool autocvar_hud_panel_scoreboard_others_showscore = true;
106 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
107 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
108 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
109 bool autocvar_hud_panel_scoreboard_playerid = false;
110 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
111 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
112 bool autocvar_hud_panel_scoreboard_scores_per_round;
114 float scoreboard_time;
118 if(autocvar_hud_panel_scoreboard_scores_per_round)
119 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
122 // mode 0: returns translated label
123 // mode 1: prints name and description of all the labels
124 string Label_getInfo(string label, int mode)
127 label = "bckills"; // first case in the switch
129 #define SCO_LABEL(strlabel, label, padding, help) \
132 return CTX(strlabel); \
133 LOG_HELP("^3", label, padding, "^7", help);
137 SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
138 SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
139 SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
140 SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
141 SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
142 SCO_LABEL(_("SCO^destroyed"), "destroyed", " ", _("Number of keys destroyed by pushing them into void"));
143 SCO_LABEL(_("SCO^damage"), "dmg", " ", _("The total damage done"));
144 SCO_LABEL(_("SCO^dmgtaken"), "dmgtaken", " ", _("The total damage taken"));
145 SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
146 SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
147 SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
148 SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
149 SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
150 SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
151 SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
152 SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
153 SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
154 SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
155 SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
156 SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
157 SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
158 SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
159 SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
160 SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
161 SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
162 SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
163 SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
164 SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
165 SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
166 SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
167 SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
168 SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
169 SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
170 SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
171 SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
172 SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
173 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
174 SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
175 SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
176 SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
177 SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
178 SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
179 SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
180 SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
181 SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
187 bool scoreboard_ui_disabling;
188 void HUD_Scoreboard_UI_Disable()
190 scoreboard_ui_disabling = true;
191 sb_showscores = false;
194 void HUD_Scoreboard_UI_Disable_Instantly()
196 scoreboard_ui_disabling = false;
197 scoreboard_ui_enabled = 0;
198 scoreboard_selected_panel = 0;
199 scoreboard_selected_player = NULL;
200 scoreboard_selected_team = NULL;
203 // mode: 0 normal, 1 team selection
204 void Scoreboard_UI_Enable(int mode)
210 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
213 // release player's pressed keys as they aren't released elsewhere
214 // in particular jump needs to be released as it may open the team selection
215 // (when server detects jump has been pressed it sends the command to open the team selection)
216 Release_Common_Keys();
217 scoreboard_ui_enabled = 2;
218 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
222 if (scoreboard_ui_enabled == 1)
224 scoreboard_ui_enabled = 1;
225 scoreboard_selected_panel = SB_PANEL_FIRST;
227 scoreboard_selected_player = NULL;
228 scoreboard_selected_team = NULL;
229 scoreboard_selected_panel_time = time;
232 int rankings_start_column;
233 int rankings_rows = 0;
234 int rankings_columns = 0;
235 int rankings_cnt = 0;
236 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
240 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
245 mousepos.x = nPrimary;
246 mousepos.y = nSecondary;
253 // at this point bInputType can only be 0 or 1 (key pressed or released)
254 bool key_pressed = (bInputType == 0);
256 // ESC to exit (TAB-ESC works too)
257 if(nPrimary == K_ESCAPE)
261 HUD_Scoreboard_UI_Disable();
265 // block any input while a menu dialog is fading
266 if(autocvar__menu_alpha)
272 // allow console bind to work
273 string con_keys = findkeysforcommand("toggleconsole", 0);
274 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
276 bool hit_con_bind = false;
278 for (i = 0; i < keys; ++i)
280 if(nPrimary == stof(argv(i)))
285 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
286 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
287 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
288 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
291 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
292 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
293 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
294 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
297 if(nPrimary == K_TAB)
301 if (scoreboard_ui_enabled == 2)
303 if (hudShiftState & S_SHIFT)
306 goto downarrow_action;
309 if (hudShiftState & S_SHIFT)
311 --scoreboard_selected_panel;
312 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
313 --scoreboard_selected_panel;
314 if (scoreboard_selected_panel < SB_PANEL_FIRST)
315 scoreboard_selected_panel = SB_PANEL_MAX;
319 ++scoreboard_selected_panel;
320 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
321 ++scoreboard_selected_panel;
322 if (scoreboard_selected_panel > SB_PANEL_MAX)
323 scoreboard_selected_panel = SB_PANEL_FIRST;
326 scoreboard_selected_panel_time = time;
328 else if(nPrimary == K_DOWNARROW)
332 LABEL(downarrow_action);
333 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
335 if (scoreboard_ui_enabled == 2)
337 entity curr_team = NULL;
338 bool scoreboard_selected_team_found = false;
339 if (!scoreboard_selected_team)
340 scoreboard_selected_team_found = true;
342 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
344 if(tm.team == NUM_SPECTATOR)
347 if (scoreboard_selected_team_found)
349 if (scoreboard_selected_team == tm)
350 scoreboard_selected_team_found = true;
353 if (curr_team == scoreboard_selected_team) // loop reached the last team
355 scoreboard_selected_team = curr_team;
360 entity curr_pl = NULL;
361 bool scoreboard_selected_player_found = false;
362 if (!scoreboard_selected_player)
363 scoreboard_selected_player_found = true;
365 for(tm = teams.sort_next; tm; tm = tm.sort_next)
367 if(tm.team != NUM_SPECTATOR)
368 for(pl = players.sort_next; pl; pl = pl.sort_next)
370 if(pl.team != tm.team)
373 if (scoreboard_selected_player_found)
375 if (scoreboard_selected_player == pl)
376 scoreboard_selected_player_found = true;
380 if (curr_pl == scoreboard_selected_player) // loop reached the last player
382 scoreboard_selected_player = curr_pl;
386 else if(nPrimary == K_UPARROW)
390 LABEL(uparrow_action);
391 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
393 if (scoreboard_ui_enabled == 2)
395 entity prev_team = NULL;
396 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
398 if(tm.team == NUM_SPECTATOR)
400 if (tm == scoreboard_selected_team)
405 scoreboard_selected_team = prev_team;
409 entity prev_pl = NULL;
411 for(tm = teams.sort_next; tm; tm = tm.sort_next)
413 if(tm.team != NUM_SPECTATOR)
414 for(pl = players.sort_next; pl; pl = pl.sort_next)
416 if(pl.team != tm.team)
418 if (pl == scoreboard_selected_player)
424 scoreboard_selected_player = prev_pl;
428 else if(nPrimary == K_RIGHTARROW)
432 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
433 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
435 else if(nPrimary == K_LEFTARROW)
439 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
440 rankings_start_column = max(rankings_start_column - 1, 0);
442 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
446 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
448 if (scoreboard_ui_enabled == 2)
451 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
454 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
455 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
456 HUD_Scoreboard_UI_Disable();
458 else if (scoreboard_selected_player)
459 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
462 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
466 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
468 switch (scoreboard_selected_columns_layout)
471 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
473 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
474 scoreboard_selected_columns_layout = 1;
479 localcmd(sprintf("scoreboard_columns_set default\n"));
480 scoreboard_selected_columns_layout = 2;
483 localcmd(sprintf("scoreboard_columns_set all\n"));
484 scoreboard_selected_columns_layout = 0;
489 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
493 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
494 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
496 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
500 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
502 if (scoreboard_selected_player)
504 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
505 HUD_Scoreboard_UI_Disable();
509 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
513 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
515 if (scoreboard_selected_player)
516 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
519 else if(hit_con_bind || nPrimary == K_PAUSE)
525 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
526 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
528 void Scoreboard_InitScores()
532 ps_primary = ps_secondary = NULL;
533 ts_primary = ts_secondary = -1;
534 FOREACH(Scores, true, {
535 if(scores_flags(it) & SFL_NOT_SORTABLE)
537 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
538 if(f == SFL_SORT_PRIO_PRIMARY)
540 if(f == SFL_SORT_PRIO_SECONDARY)
543 if(ps_secondary == NULL)
544 ps_secondary = ps_primary;
546 for(i = 0; i < MAX_TEAMSCORE; ++i)
548 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
549 if(f == SFL_SORT_PRIO_PRIMARY)
551 if(f == SFL_SORT_PRIO_SECONDARY)
554 if(ts_secondary == -1)
555 ts_secondary = ts_primary;
557 Cmd_Scoreboard_SetFields(0);
561 void Scoreboard_UpdatePlayerTeams()
563 static float update_time;
564 if (time <= update_time)
571 for(pl = players.sort_next; pl; pl = pl.sort_next)
573 numplayers += pl.team != NUM_SPECTATOR;
575 int Team = entcs_GetScoreTeam(pl.sv_entnum);
576 if(SetTeam(pl, Team))
579 Scoreboard_UpdatePlayerPos(pl);
583 pl = players.sort_next;
588 print(strcat("PNUM: ", ftos(num), "\n"));
593 int Scoreboard_CompareScore(int vl, int vr, int f)
595 TC(int, vl); TC(int, vr); TC(int, f);
596 if(f & SFL_ZERO_IS_WORST)
598 if(vl == 0 && vr != 0)
600 if(vl != 0 && vr == 0)
604 return IS_INCREASING(f);
606 return IS_DECREASING(f);
610 float Scoreboard_ComparePlayerScores(entity left, entity right)
612 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
613 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
620 if(vl == NUM_SPECTATOR)
622 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
624 if(!left.gotscores && right.gotscores)
629 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
630 if (res >= 0) return res;
632 if (ps_secondary && ps_secondary != ps_primary)
634 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
635 if (res >= 0) return res;
638 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
639 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
640 if (res >= 0) return res;
643 if (left.sv_entnum < right.sv_entnum)
649 void Scoreboard_UpdatePlayerPos(entity player)
652 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
654 SORT_SWAP(player, ent);
656 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
658 SORT_SWAP(ent, player);
662 float Scoreboard_CompareTeamScores(entity left, entity right)
664 if(left.team == NUM_SPECTATOR)
666 if(right.team == NUM_SPECTATOR)
671 for(int i = -2; i < MAX_TEAMSCORE; ++i)
675 if (fld_idx == -1) fld_idx = ts_primary;
676 else if (ts_secondary == ts_primary) continue;
677 else fld_idx = ts_secondary;
682 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
685 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
686 if (r >= 0) return r;
689 if (left.team < right.team)
695 void Scoreboard_UpdateTeamPos(entity Team)
698 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
700 SORT_SWAP(Team, ent);
702 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
704 SORT_SWAP(ent, Team);
708 void Cmd_Scoreboard_Help()
710 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
711 LOG_HELP(_("Usage:"));
712 LOG_HELP("^2scoreboard_columns_set ^3default");
713 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
714 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
715 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
716 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
717 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
718 LOG_HELP(_("The following field names are recognized (case insensitive):"));
724 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
725 "of game types, then a slash, to make the field show up only in these\n"
726 "or in all but these game types. You can also specify 'all' as a\n"
727 "field to show all fields available for the current game mode."));
730 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
731 "include/exclude ALL teams/noteams game modes."));
734 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
735 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
736 "right of the vertical bar aligned to the right."));
737 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
738 "other gamemodes except DM."));
741 // NOTE: adding a gametype with ? to not warn for an optional field
742 // make sure it's excluded in a previous exclusive rule, if any
743 // otherwise the previous exclusive rule warns anyway
744 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
745 #define SCOREBOARD_DEFAULT_COLUMNS \
746 "ping pl fps name |" \
747 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
748 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
750 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
751 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
752 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
753 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
754 " +surv/survivals +surv/hunts" \
755 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
756 " +lms/lives +lms/rank" \
757 " +kh/kckills +kh/losses +kh/caps" \
758 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
759 " +as/objectives +nb/faults +nb/goals" \
760 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
761 " +dom/ticks +dom/takes" \
762 " -lms,rc,cts,inv,nb/score"
764 void Cmd_Scoreboard_SetFields(int argc)
769 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
773 return; // do nothing, we don't know gametype and scores yet
775 // sbt_fields uses strunzone on the titles!
776 if(!sbt_field_title[0])
777 for(i = 0; i < MAX_SBT_FIELDS; ++i)
778 sbt_field_title[i] = strzone("(null)");
780 // TODO: re enable with gametype dependant cvars?
781 if(argc < 3) // no arguments provided
782 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
785 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
789 if(argv(2) == "default" || argv(2) == "expand_default")
791 if(argv(2) == "expand_default")
792 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
793 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
795 else if(argv(2) == "all" || argv(2) == "ALL")
797 string s = "ping pl name |"; // scores without label (not really scores)
800 // scores without label
801 s = strcat(s, " ", "sum");
802 s = strcat(s, " ", "kdratio");
803 s = strcat(s, " ", "frags");
805 FOREACH(Scores, true, {
807 if(it != ps_secondary)
808 if(scores_label(it) != "")
809 s = strcat(s, " ", scores_label(it));
811 if(ps_secondary != ps_primary)
812 s = strcat(s, " ", scores_label(ps_secondary));
813 s = strcat(s, " ", scores_label(ps_primary));
814 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
821 hud_fontsize = HUD_GetFontsize("hud_fontsize");
823 for(i = 1; i < argc - 1; ++i)
826 bool nocomplain = false;
827 if(substring(str, 0, 1) == "?")
830 str = substring(str, 1, strlen(str) - 1);
833 slash = strstrofs(str, "/", 0);
836 pattern = substring(str, 0, slash);
837 str = substring(str, slash + 1, strlen(str) - (slash + 1));
839 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
843 str = strtolower(str);
844 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
849 // fields without a label (not networked via the score system)
850 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
851 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
852 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
853 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
854 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
855 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
856 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
857 default: // fields with a label
859 // map alternative labels
860 if (str == "damage") str = "dmg";
861 if (str == "damagetaken") str = "dmgtaken";
863 FOREACH(Scores, true, {
864 if (str == strtolower(scores_label(it))) {
866 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
870 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
871 if(!nocomplain && str != "fps") // server can disable the fps field
872 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
874 strfree(sbt_field_title[sbt_num_fields]);
878 sbt_field[sbt_num_fields] = j;
881 if(j == ps_secondary)
882 have_secondary = true;
887 if(sbt_num_fields >= MAX_SBT_FIELDS)
891 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
893 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
894 have_secondary = true;
895 if(ps_primary == ps_secondary)
896 have_secondary = true;
897 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
899 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
903 strfree(sbt_field_title[sbt_num_fields]);
904 for(i = sbt_num_fields; i > 0; --i)
906 sbt_field_title[i] = sbt_field_title[i-1];
907 sbt_field[i] = sbt_field[i-1];
909 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
910 sbt_field[0] = SP_NAME;
912 LOG_INFO("fixed missing field 'name'");
916 strfree(sbt_field_title[sbt_num_fields]);
917 for(i = sbt_num_fields; i > 1; --i)
919 sbt_field_title[i] = sbt_field_title[i-1];
920 sbt_field[i] = sbt_field[i-1];
922 sbt_field_title[1] = strzone("|");
923 sbt_field[1] = SP_SEPARATOR;
925 LOG_INFO("fixed missing field '|'");
928 else if(!have_separator)
930 strcpy(sbt_field_title[sbt_num_fields], "|");
931 sbt_field[sbt_num_fields] = SP_SEPARATOR;
933 LOG_INFO("fixed missing field '|'");
937 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
938 sbt_field[sbt_num_fields] = ps_secondary;
940 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
944 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
945 sbt_field[sbt_num_fields] = ps_primary;
947 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
951 sbt_field[sbt_num_fields] = SP_END;
952 sbt_field_size[0] = 0; // tells Scoreboard_Draw to initialize all field sizes
955 string Scoreboard_AddPlayerId(string pl_name, entity pl)
957 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
958 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
959 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
963 vector sbt_field_rgb;
964 string sbt_field_icon0;
965 string sbt_field_icon1;
966 string sbt_field_icon2;
967 vector sbt_field_icon0_rgb;
968 vector sbt_field_icon1_rgb;
969 vector sbt_field_icon2_rgb;
970 string Scoreboard_GetName(entity pl)
972 if(ready_waiting && pl.ready)
974 sbt_field_icon0 = "gfx/scoreboard/player_ready";
979 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
980 if (playerslots[pl.sv_entnum].colormap >= 1024)
981 f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
983 f = entcs_GetClientColors(pl.sv_entnum);
986 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
987 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
988 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
989 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
990 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
993 return entcs_GetName(pl.sv_entnum);
996 int autocvar_hud_panel_scoreboard_ping_best = 0;
997 int autocvar_hud_panel_scoreboard_ping_medium = 70;
998 int autocvar_hud_panel_scoreboard_ping_high = 100;
999 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1000 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1001 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1002 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1003 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1004 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1005 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1006 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1007 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1008 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1009 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1010 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1011 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1012 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1014 float tmp, num, denom;
1017 sbt_field_rgb = '1 1 1';
1018 sbt_field_icon0 = "";
1019 sbt_field_icon1 = "";
1020 sbt_field_icon2 = "";
1021 sbt_field_icon0_rgb = '1 1 1';
1022 sbt_field_icon1_rgb = '1 1 1';
1023 sbt_field_icon2_rgb = '1 1 1';
1024 int rounds_played = 0;
1026 rounds_played = pl.(scores(SP_ROUNDS_PL));
1031 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1032 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1037 sbt_field_rgb = COLOR_BEST;
1038 else if(f < PING_MED)
1039 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1040 else if(f < PING_HIGH)
1041 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1042 else if(f < PING_WORST)
1043 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1045 sbt_field_rgb = COLOR_WORST;
1051 f = pl.ping_packetloss;
1052 tmp = pl.ping_movementloss;
1053 if(f == 0 && tmp == 0)
1055 str = ftos(ceil(f * 100));
1057 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1058 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1059 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1063 str = Scoreboard_GetName(pl);
1064 if (autocvar_hud_panel_scoreboard_playerid)
1065 str = Scoreboard_AddPlayerId(str, pl);
1069 f = pl.(scores(SP_KILLS));
1070 f -= pl.(scores(SP_SUICIDES));
1072 return sprintf("%.1f", f / rounds_played);
1076 num = pl.(scores(SP_KILLS));
1077 denom = pl.(scores(SP_DEATHS));
1080 sbt_field_rgb = '0 1 0';
1082 str = sprintf("%.1f", num / rounds_played);
1084 str = sprintf("%d", num);
1085 } else if(num <= 0) {
1086 sbt_field_rgb = '1 0 0';
1088 str = sprintf("%.2f", num / (denom * rounds_played));
1090 str = sprintf("%.1f", num / denom);
1094 str = sprintf("%.2f", num / (denom * rounds_played));
1096 str = sprintf("%.1f", num / denom);
1101 f = pl.(scores(SP_KILLS));
1102 f -= pl.(scores(SP_DEATHS));
1105 sbt_field_rgb = '0 1 0';
1107 sbt_field_rgb = '1 1 1';
1109 sbt_field_rgb = '1 0 0';
1112 return sprintf("%.1f", f / rounds_played);
1117 float elo = pl.(scores(SP_ELO));
1119 case -1: return "...";
1120 case -2: return _("N/A");
1121 default: return ftos(elo);
1127 float fps = pl.(scores(SP_FPS));
1130 sbt_field_rgb = '1 1 1';
1131 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1133 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1134 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1139 return ftos(pl.(scores(field)));
1141 case SP_DMG: case SP_DMGTAKEN:
1143 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1144 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1146 default: case SP_SCORE:
1147 tmp = pl.(scores(field));
1148 f = scores_flags(field);
1149 if(field == ps_primary)
1150 sbt_field_rgb = '1 1 0';
1151 else if(field == ps_secondary)
1152 sbt_field_rgb = '0 1 1';
1154 sbt_field_rgb = '1 1 1';
1155 return ScoreString(f, tmp, rounds_played);
1160 float sbt_fixcolumnwidth_len;
1161 float sbt_fixcolumnwidth_iconlen;
1162 float sbt_fixcolumnwidth_marginlen;
1164 string Scoreboard_FixColumnWidth(int i, string str, bool init)
1170 sbt_fixcolumnwidth_iconlen = 0;
1172 if(sbt_field_icon0 != "")
1174 sz = draw_getimagesize(sbt_field_icon0);
1176 if(sbt_fixcolumnwidth_iconlen < f)
1177 sbt_fixcolumnwidth_iconlen = f;
1180 if(sbt_field_icon1 != "")
1182 sz = draw_getimagesize(sbt_field_icon1);
1184 if(sbt_fixcolumnwidth_iconlen < f)
1185 sbt_fixcolumnwidth_iconlen = f;
1188 if(sbt_field_icon2 != "")
1190 sz = draw_getimagesize(sbt_field_icon2);
1192 if(sbt_fixcolumnwidth_iconlen < f)
1193 sbt_fixcolumnwidth_iconlen = f;
1196 if(sbt_fixcolumnwidth_iconlen != 0)
1198 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1199 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1202 sbt_fixcolumnwidth_marginlen = 0;
1205 sbt_field_title_width[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1207 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1210 float remaining_space = 0;
1211 for(j = 0; j < sbt_num_fields; ++j)
1213 if (sbt_field[i] != SP_SEPARATOR)
1214 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1215 sbt_field_size[i] = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1217 if (sbt_fixcolumnwidth_iconlen != 0)
1218 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1219 float namesize = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1220 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1221 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1223 max_namesize = max(sbt_field_title_width[i], vid_conwidth - remaining_space);
1229 sbt_field_size[i] = sbt_field_title_width[i];
1230 if (sbt_field_size[i] && sbt_field_size[i] > sbt_field_title_maxwidth)
1231 sbt_field_size[i] = sbt_field_title_maxwidth;
1233 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1236 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1237 if(sbt_field_size[i] < f)
1238 sbt_field_size[i] = f;
1240 sbt_field_title_condense_factor[i] = 0;
1241 if (sbt_field_title_width[i] > sbt_field_size[i])
1243 float real_maxwidth = sbt_field_size[i];
1244 if (sbt_field_title_width[i] > sbt_field_title_maxwidth)
1245 real_maxwidth = max(sbt_field_size[i], sbt_field_title_maxwidth);
1246 sbt_field_title_condense_factor[i] = real_maxwidth / sbt_field_title_width[i];
1252 void Scoreboard_initFieldSizes()
1255 for(int i = 0; i < sbt_num_fields; ++i)
1257 if (sbt_field[i] == SP_NAME)
1263 Scoreboard_FixColumnWidth(i, "", true);
1266 // update name field size in the end as it takes remaining space
1267 Scoreboard_FixColumnWidth(name_index, "", true);
1270 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1273 vector column_dim = eY * panel_size.y;
1275 column_dim.y -= 1.25 * hud_fontsize.y;
1276 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1277 pos.x += hud_fontsize.x * 0.5;
1278 for(i = 0; i < sbt_num_fields; ++i)
1280 if(sbt_field[i] == SP_SEPARATOR)
1282 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1285 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1286 vector prev_drawfontscale = drawfontscale;
1287 if (sbt_field_title_condense_factor[i])
1288 drawfontscale.x *= sbt_field_title_condense_factor[i];
1289 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1290 if (sbt_field_title_condense_factor[i])
1292 drawfontscale.x *= sbt_field_title_condense_factor[i];
1293 drawfontscale = prev_drawfontscale;
1295 pos.x += column_dim.x;
1297 if(sbt_field[i] == SP_SEPARATOR)
1299 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1300 for(i = sbt_num_fields - 1; i > 0; --i)
1302 if(sbt_field[i] == SP_SEPARATOR)
1305 pos.x -= sbt_field_size[i];
1310 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1311 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1314 vector prev_drawfontscale = drawfontscale;
1315 float titlewidth = stringwidth(sbt_field_title[i], false, hud_fontsize);
1316 if (sbt_field_title_condense_factor[i])
1318 drawfontscale.x *= sbt_field_title_condense_factor[i];
1319 text_offset.x = sbt_field_size[i] - titlewidth * sbt_field_title_condense_factor[i];
1322 text_offset.x = sbt_field_size[i] - titlewidth;
1323 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1324 if (sbt_field_title_condense_factor[i])
1326 drawfontscale.x *= sbt_field_title_condense_factor[i];
1327 drawfontscale = prev_drawfontscale;
1329 pos.x -= hud_fontsize.x;
1333 pos.x = panel_pos.x;
1334 pos.y += 1.25 * hud_fontsize.y;
1338 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1340 TC(bool, is_self); TC(int, pl_number);
1342 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1344 vector h_pos = item_pos;
1345 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1346 // alternated rows highlighting
1347 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1349 if (pl == scoreboard_selected_player)
1350 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1353 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1354 else if((sbt_highlight) && (!(pl_number % 2)))
1355 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1357 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1359 vector pos = item_pos;
1360 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1362 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1364 pos.x += hud_fontsize.x * 0.5;
1365 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1366 vector tmp = '0 0 0';
1368 PlayerScoreField field;
1369 for(i = 0; i < sbt_num_fields; ++i)
1371 field = sbt_field[i];
1372 if(field == SP_SEPARATOR)
1375 if(is_spec && field != SP_NAME && field != SP_PING) {
1376 pos.x += sbt_field_size[i] + hud_fontsize.x;
1379 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1380 str = Scoreboard_FixColumnWidth(i, str, false);
1382 pos.x += sbt_field_size[i] + hud_fontsize.x;
1384 if(field == SP_NAME) {
1385 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1386 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1388 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1389 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1392 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1393 if(sbt_field_icon0 != "")
1394 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1395 if(sbt_field_icon1 != "")
1396 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1397 if(sbt_field_icon2 != "")
1398 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1401 if(sbt_field[i] == SP_SEPARATOR)
1403 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1404 for(i = sbt_num_fields-1; i > 0; --i)
1406 field = sbt_field[i];
1407 if(field == SP_SEPARATOR)
1410 if(is_spec && field != SP_NAME && field != SP_PING) {
1411 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1415 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1416 str = Scoreboard_FixColumnWidth(i, str, false);
1418 if(field == SP_NAME) {
1419 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1420 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1422 tmp.x = sbt_fixcolumnwidth_len;
1423 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1426 tmp.x = sbt_field_size[i];
1427 if(sbt_field_icon0 != "")
1428 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1429 if(sbt_field_icon1 != "")
1430 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1431 if(sbt_field_icon2 != "")
1432 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1433 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1438 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1441 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1444 vector h_pos = item_pos;
1445 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1447 bool complete = (this_team == NUM_SPECTATOR);
1450 if((sbt_highlight) && (!(pl_number % 2)))
1451 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1453 vector pos = item_pos;
1454 pos.x += hud_fontsize.x * 0.5;
1455 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1457 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1459 width_limit -= stringwidth("...", false, hud_fontsize);
1460 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1461 static float max_name_width = 0;
1463 float fieldsize = 0;
1464 float min_fieldsize = 0;
1465 float fieldpadding = hud_fontsize.x * 0.25;
1466 if(this_team == NUM_SPECTATOR)
1468 if(autocvar_hud_panel_scoreboard_spectators_showping)
1469 min_fieldsize = stringwidth("999", false, hud_fontsize);
1471 else if(autocvar_hud_panel_scoreboard_others_showscore)
1472 min_fieldsize = stringwidth("99", false, hud_fontsize);
1473 for(i = 0; pl; pl = pl.sort_next)
1475 if(pl.team != this_team)
1477 if(pl == ignored_pl)
1481 if(this_team == NUM_SPECTATOR)
1483 if(autocvar_hud_panel_scoreboard_spectators_showping)
1484 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1486 else if(autocvar_hud_panel_scoreboard_others_showscore)
1487 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1489 string str = entcs_GetName(pl.sv_entnum);
1490 if (autocvar_hud_panel_scoreboard_playerid)
1491 str = Scoreboard_AddPlayerId(str, pl);
1492 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1493 float column_width = stringwidth(str, true, hud_fontsize);
1494 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1496 if(column_width > max_name_width)
1497 max_name_width = column_width;
1498 column_width = max_name_width;
1502 fieldsize = stringwidth(field, false, hud_fontsize);
1503 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1506 if(pos.x + column_width > width_limit)
1511 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1516 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1517 pos.y += hud_fontsize.y * 1.25;
1521 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1523 if (pl == scoreboard_selected_player)
1525 h_size.x = column_width + hud_fontsize.x * 0.25;
1526 h_size.y = hud_fontsize.y;
1527 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1531 vector name_pos = pos;
1532 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1533 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1534 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1537 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1538 h_size.y = hud_fontsize.y;
1539 vector field_pos = pos;
1540 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1541 field_pos.x += column_width - h_size.x;
1543 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1544 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1545 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1549 h_size.x = column_width + hud_fontsize.x * 0.25;
1550 h_size.y = hud_fontsize.y;
1551 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1553 pos.x += column_width;
1554 pos.x += hud_fontsize.x;
1556 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1559 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1561 int max_players = 999;
1562 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1564 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1567 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1568 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1569 height /= team_count;
1572 height -= panel_bg_padding * 2; // - padding
1573 max_players = floor(height / (hud_fontsize.y * 1.25));
1574 if(max_players <= 1)
1576 if(max_players == tm.team_size)
1581 entity me = playerslots[current_player];
1583 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1584 panel_size.y += panel_bg_padding * 2;
1586 vector scoreboard_selected_hl_pos = pos;
1587 vector scoreboard_selected_hl_size = '0 0 0';
1588 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1589 scoreboard_selected_hl_size.y = panel_size.y;
1593 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1594 if(panel.current_panel_bg != "0")
1595 end_pos.y += panel_bg_border * 2;
1597 if(panel_bg_padding)
1599 panel_pos += '1 1 0' * panel_bg_padding;
1600 panel_size -= '2 2 0' * panel_bg_padding;
1604 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1608 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1610 pos.y += 1.25 * hud_fontsize.y;
1613 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1615 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1618 // print header row and highlight columns
1619 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1621 // fill the table and draw the rows
1622 bool is_self = false;
1623 bool self_shown = false;
1625 for(pl = players.sort_next; pl; pl = pl.sort_next)
1627 if(pl.team != tm.team)
1629 if(i == max_players - 2 && pl != me)
1631 if(!self_shown && me.team == tm.team)
1633 Scoreboard_DrawItem(pos, rgb, me, true, i);
1635 pos.y += 1.25 * hud_fontsize.y;
1639 if(i >= max_players - 1)
1641 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1644 is_self = (pl.sv_entnum == current_player);
1645 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1648 pos.y += 1.25 * hud_fontsize.y;
1652 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1654 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1656 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1657 _alpha *= panel_fg_alpha;
1659 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1663 panel_size.x += panel_bg_padding * 2; // restore initial width
1667 bool Scoreboard_WouldDraw()
1669 if (scoreboard_ui_enabled)
1671 if (scoreboard_ui_disabling)
1673 if (scoreboard_fade_alpha == 0)
1674 HUD_Scoreboard_UI_Disable_Instantly();
1677 if (intermission && scoreboard_ui_enabled == 2)
1679 HUD_Scoreboard_UI_Disable_Instantly();
1684 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1686 else if (QuickMenu_IsOpened())
1688 else if (HUD_Radar_Clickable())
1690 else if (sb_showscores) // set by +showscores engine command
1692 else if (intermission == 1)
1694 else if (intermission == 2)
1696 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1697 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1701 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1706 float average_accuracy;
1707 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1709 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1711 WepSet weapons_stat = WepSet_GetFromStat();
1712 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1713 int disownedcnt = 0;
1715 FOREACH(Weapons, it != WEP_Null, {
1716 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1718 WepSet set = it.m_wepset;
1719 if(it.spawnflags & WEP_TYPE_OTHER)
1724 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1726 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1733 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1734 if (weapon_cnt <= 0) return pos;
1737 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1739 int columns = ceil(weapon_cnt / rows);
1741 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1742 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1743 float height = weapon_height + hud_fontsize.y;
1745 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);
1746 pos.y += 1.25 * hud_fontsize.y;
1747 if(panel.current_panel_bg != "0")
1748 pos.y += panel_bg_border;
1751 panel_size.y = height * rows;
1752 panel_size.y += panel_bg_padding * 2;
1754 float panel_bg_alpha_save = panel_bg_alpha;
1755 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1757 panel_bg_alpha = panel_bg_alpha_save;
1759 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1760 if(panel.current_panel_bg != "0")
1761 end_pos.y += panel_bg_border * 2;
1763 if(panel_bg_padding)
1765 panel_pos += '1 1 0' * panel_bg_padding;
1766 panel_size -= '2 2 0' * panel_bg_padding;
1770 vector tmp = panel_size;
1772 float weapon_width = tmp.x / columns / rows;
1775 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1779 // column highlighting
1780 for (int i = 0; i < columns; ++i)
1782 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);
1785 for (int i = 0; i < rows; ++i)
1786 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1789 average_accuracy = 0;
1790 int weapons_with_stats = 0;
1792 pos.x += weapon_width / 2;
1794 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1797 Accuracy_LoadColors();
1799 float oldposx = pos.x;
1803 FOREACH(Weapons, it != WEP_Null, {
1804 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1806 WepSet set = it.m_wepset;
1807 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1809 if (it.spawnflags & WEP_TYPE_OTHER)
1813 if (weapon_stats >= 0)
1814 weapon_alpha = sbt_fg_alpha;
1816 weapon_alpha = 0.2 * sbt_fg_alpha;
1819 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1821 if (weapon_stats >= 0) {
1822 weapons_with_stats += 1;
1823 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1825 string s = sprintf("%d%%", weapon_stats * 100);
1826 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1828 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1829 rgb = Accuracy_GetColor(weapon_stats);
1831 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1833 tmpos.x += weapon_width * rows;
1834 pos.x += weapon_width * rows;
1835 if (rows == 2 && column == columns - 1) {
1843 if (weapons_with_stats)
1844 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1846 panel_size.x += panel_bg_padding * 2; // restore initial width
1851 bool is_item_filtered(entity it)
1853 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1855 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1858 if (it.instanceOfArmor || it.instanceOfHealth)
1860 int ha_mask = floor(mask) % 10;
1863 default: return false;
1864 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1865 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1866 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1867 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1870 if (it.instanceOfAmmo)
1872 int ammo_mask = floor(mask / 10) % 10;
1873 return (ammo_mask == 1);
1878 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1880 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1882 int disowned_cnt = 0;
1883 int uninteresting_cnt = 0;
1884 IL_EACH(default_order_items, true, {
1885 int q = g_inventory.inv_items[it.m_id];
1886 //q = 1; // debug: display all items
1887 if (is_item_filtered(it))
1888 ++uninteresting_cnt;
1892 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1893 int n = items_cnt - disowned_cnt;
1894 if (n <= 0) return pos;
1896 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1897 int columns = max(6, ceil(n / rows));
1899 float item_height = hud_fontsize.y * 2.3;
1900 float height = item_height + hud_fontsize.y;
1902 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1903 pos.y += 1.25 * hud_fontsize.y;
1904 if(panel.current_panel_bg != "0")
1905 pos.y += panel_bg_border;
1908 panel_size.y = height * rows;
1909 panel_size.y += panel_bg_padding * 2;
1911 float panel_bg_alpha_save = panel_bg_alpha;
1912 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1914 panel_bg_alpha = panel_bg_alpha_save;
1916 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1917 if(panel.current_panel_bg != "0")
1918 end_pos.y += panel_bg_border * 2;
1920 if(panel_bg_padding)
1922 panel_pos += '1 1 0' * panel_bg_padding;
1923 panel_size -= '2 2 0' * panel_bg_padding;
1927 vector tmp = panel_size;
1929 float item_width = tmp.x / columns / rows;
1932 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1936 // column highlighting
1937 for (int i = 0; i < columns; ++i)
1939 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);
1942 for (int i = 0; i < rows; ++i)
1943 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1947 pos.x += item_width / 2;
1949 float oldposx = pos.x;
1953 IL_EACH(default_order_items, !is_item_filtered(it), {
1954 int n = g_inventory.inv_items[it.m_id];
1955 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1956 if (n <= 0) continue;
1957 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);
1959 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1960 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1961 tmpos.x += item_width * rows;
1962 pos.x += item_width * rows;
1963 if (rows == 2 && column == columns - 1) {
1971 panel_size.x += panel_bg_padding * 2; // restore initial width
1976 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1978 pos.x += hud_fontsize.x * 0.25;
1979 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1980 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1981 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1983 pos.y += hud_fontsize.y;
1988 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1989 float stat_secrets_found, stat_secrets_total;
1990 float stat_monsters_killed, stat_monsters_total;
1994 // get monster stats
1995 stat_monsters_killed = STAT(MONSTERS_KILLED);
1996 stat_monsters_total = STAT(MONSTERS_TOTAL);
1998 // get secrets stats
1999 stat_secrets_found = STAT(SECRETS_FOUND);
2000 stat_secrets_total = STAT(SECRETS_TOTAL);
2002 // get number of rows
2003 if(stat_secrets_total)
2005 if(stat_monsters_total)
2008 // if no rows, return
2012 // draw table header
2013 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2014 pos.y += 1.25 * hud_fontsize.y;
2015 if(panel.current_panel_bg != "0")
2016 pos.y += panel_bg_border;
2019 panel_size.y = hud_fontsize.y * rows;
2020 panel_size.y += panel_bg_padding * 2;
2023 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2024 if(panel.current_panel_bg != "0")
2025 end_pos.y += panel_bg_border * 2;
2027 if(panel_bg_padding)
2029 panel_pos += '1 1 0' * panel_bg_padding;
2030 panel_size -= '2 2 0' * panel_bg_padding;
2034 vector tmp = panel_size;
2037 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2040 if(stat_monsters_total)
2042 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2043 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2047 if(stat_secrets_total)
2049 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2050 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2053 panel_size.x += panel_bg_padding * 2; // restore initial width
2057 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2060 RANKINGS_RECEIVED_CNT = 0;
2061 for (i=RANKINGS_CNT-1; i>=0; --i)
2063 ++RANKINGS_RECEIVED_CNT;
2065 if (RANKINGS_RECEIVED_CNT == 0)
2068 vector hl_rgb = rgb + '0.5 0.5 0.5';
2070 vector scoreboard_selected_hl_pos = pos;
2072 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2073 pos.y += 1.25 * hud_fontsize.y;
2074 if(panel.current_panel_bg != "0")
2075 pos.y += panel_bg_border;
2077 vector scoreboard_selected_hl_size = '0 0 0';
2078 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2079 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2084 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2086 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2091 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2093 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2097 float ranksize = 3 * hud_fontsize.x;
2098 float timesize = 5 * hud_fontsize.x;
2099 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2100 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2101 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2104 rankings_cnt = RANKINGS_RECEIVED_CNT;
2105 rankings_rows = ceil(rankings_cnt / rankings_columns);
2108 // expand name column to fill the entire row
2109 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2110 namesize += available_space;
2111 columnsize.x += available_space;
2113 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2114 panel_size.y += panel_bg_padding * 2;
2115 scoreboard_selected_hl_size.y += panel_size.y;
2119 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2120 if(panel.current_panel_bg != "0")
2121 end_pos.y += panel_bg_border * 2;
2123 if(panel_bg_padding)
2125 panel_pos += '1 1 0' * panel_bg_padding;
2126 panel_size -= '2 2 0' * panel_bg_padding;
2132 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2134 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2136 int column = 0, j = 0;
2137 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2138 int start_item = rankings_start_column * rankings_rows;
2139 for(i = start_item; i < start_item + rankings_cnt; ++i)
2141 int t = grecordtime[i];
2145 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2146 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2147 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2148 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2150 str = count_ordinal(i+1);
2151 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2152 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2153 str = ColorTranslateRGB(grecordholder[i]);
2155 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2156 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2158 pos.y += 1.25 * hud_fontsize.y;
2160 if(j >= rankings_rows)
2164 pos.x += panel_size.x / rankings_columns;
2165 pos.y = panel_pos.y;
2168 strfree(zoned_name_self);
2170 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2172 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2173 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2176 panel_size.x += panel_bg_padding * 2; // restore initial width
2180 bool have_weapon_stats;
2181 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2183 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2185 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2188 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2189 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2195 if (!have_weapon_stats)
2197 FOREACH(Weapons, it != WEP_Null, {
2198 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2199 if (weapon_stats >= 0)
2201 have_weapon_stats = true;
2205 if (!have_weapon_stats)
2212 bool have_item_stats;
2213 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2215 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2217 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2220 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2221 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2227 if (!have_item_stats)
2229 IL_EACH(default_order_items, true, {
2230 if (!is_item_filtered(it))
2232 int q = g_inventory.inv_items[it.m_id];
2233 //q = 1; // debug: display all items
2236 have_item_stats = true;
2241 if (!have_item_stats)
2248 vector Scoreboard_Spectators_Draw(vector pos) {
2253 for(pl = players.sort_next; pl; pl = pl.sort_next)
2255 if(pl.team == NUM_SPECTATOR)
2257 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2258 if(tm.team == NUM_SPECTATOR)
2260 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2261 draw_beginBoldFont();
2262 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2264 pos.y += 1.25 * hud_fontsize.y;
2266 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2267 pos.y += 1.25 * hud_fontsize.y;
2272 if (str != "") // if there's at least one spectator
2273 pos.y += 0.5 * hud_fontsize.y;
2278 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2280 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2281 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2282 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2283 (s_label == "score") ? CTX(_("SCO^points")) :
2284 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2287 void Scoreboard_Draw()
2289 bool sb_init_field_sizes = false;
2291 if(!autocvar__hud_configure)
2293 if(!hud_draw_maximized) return;
2295 // frametime checks allow to toggle the scoreboard even when the game is paused
2296 if(scoreboard_active) {
2297 if (scoreboard_fade_alpha == 0)
2298 scoreboard_time = time;
2299 if(hud_configure_menu_open == 1)
2300 scoreboard_fade_alpha = 1;
2301 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2302 if (scoreboard_fadeinspeed && frametime)
2303 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2305 scoreboard_fade_alpha = 1;
2307 static string hud_fontsize_str;
2308 if(hud_fontsize_str != autocvar_hud_fontsize)
2310 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2311 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2312 sb_init_field_sizes = true;
2315 static float scoreboard_table_fieldtitle_maxwidth_prev;
2316 if (scoreboard_table_fieldtitle_maxwidth_prev != autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth)
2318 scoreboard_table_fieldtitle_maxwidth_prev = autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth;
2319 sbt_field_title_maxwidth = vid_conwidth * max(0.01, autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth);
2320 sb_init_field_sizes = true;
2324 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2325 if (scoreboard_fadeoutspeed && frametime)
2326 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2328 scoreboard_fade_alpha = 0;
2331 if (!scoreboard_fade_alpha)
2333 scoreboard_acc_fade_alpha = 0;
2334 scoreboard_itemstats_fade_alpha = 0;
2339 scoreboard_fade_alpha = 0;
2341 if (autocvar_hud_panel_scoreboard_dynamichud)
2344 HUD_Scale_Disable();
2346 if(scoreboard_fade_alpha <= 0)
2348 panel_fade_alpha *= scoreboard_fade_alpha;
2349 HUD_Panel_LoadCvars();
2351 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2352 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2353 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2354 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2355 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2356 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2357 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2359 // don't overlap with con_notify
2360 if(!autocvar__hud_configure)
2361 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2363 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2364 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2365 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2366 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2367 panel_pos.x = scoreboard_left;
2368 panel_size.x = fixed_scoreboard_width;
2370 // field sizes can be initialized now after panel_size.x calculation
2371 if (!sbt_field_size[0] || sb_init_field_sizes)
2372 Scoreboard_initFieldSizes();
2374 Scoreboard_UpdatePlayerTeams();
2376 scoreboard_top = panel_pos.y;
2377 vector pos = panel_pos;
2382 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2384 // Begin of Game Info Section
2385 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2386 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2388 // Game Info: Game Type
2389 if (scoreboard_ui_enabled == 2)
2390 str = _("Team Selection");
2391 else if (gametype_custom_name != "")
2392 str = gametype_custom_name;
2394 str = MapInfo_Type_ToText(gametype);
2395 draw_beginBoldFont();
2396 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);
2399 pos.y += sb_gameinfo_type_fontsize.y;
2400 // Game Info: Game Detail
2401 if (scoreboard_ui_enabled == 2)
2403 if (scoreboard_selected_team)
2404 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2406 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2407 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);
2409 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2410 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2411 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);
2415 float tl = STAT(TIMELIMIT);
2416 float fl = STAT(FRAGLIMIT);
2417 float ll = STAT(LEADLIMIT);
2418 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2421 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2422 if(!gametype.m_hidelimits)
2427 str = strcat(str, "^7 / "); // delimiter
2428 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2432 if(tl > 0 || fl > 0)
2435 if (ll_and_fl && fl > 0)
2436 str = strcat(str, "^7 & ");
2438 str = strcat(str, "^7 / ");
2440 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2443 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
2444 // map name and player count
2448 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2449 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2450 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2452 // End of Game Info Section
2454 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2455 if(panel.current_panel_bg != "0")
2456 pos.y += panel_bg_border;
2458 // Draw the scoreboard
2459 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2462 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2466 vector panel_bg_color_save = panel_bg_color;
2467 vector team_score_baseoffset;
2468 vector team_size_baseoffset;
2469 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2471 // put team score to the left of scoreboard (and team size to the right)
2472 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2473 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2474 if(panel.current_panel_bg != "0")
2476 team_score_baseoffset.x -= panel_bg_border;
2477 team_size_baseoffset.x += panel_bg_border;
2482 // put team score to the right of scoreboard (and team size to the left)
2483 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2484 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2485 if(panel.current_panel_bg != "0")
2487 team_score_baseoffset.x += panel_bg_border;
2488 team_size_baseoffset.x -= panel_bg_border;
2492 int team_size_total = 0;
2493 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2495 // calculate team size total (sum of all team sizes)
2496 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2497 if(tm.team != NUM_SPECTATOR)
2498 team_size_total += tm.team_size;
2501 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2503 if(tm.team == NUM_SPECTATOR)
2508 draw_beginBoldFont();
2509 vector rgb = Team_ColorRGB(tm.team);
2510 str = ftos(tm.(teamscores(ts_primary)));
2511 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2513 // team score on the left (default)
2514 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2518 // team score on the right
2519 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2521 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2523 // team size (if set to show on the side)
2524 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2526 // calculate the starting position for the whole team size info string
2527 str = sprintf("%d/%d", tm.team_size, team_size_total);
2528 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2530 // team size on the left
2531 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2535 // team size on the right
2536 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2538 str = sprintf("%d", tm.team_size);
2539 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2540 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2541 str = sprintf("/%d", team_size_total);
2542 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2546 // secondary score, e.g. keyhunt
2547 if(ts_primary != ts_secondary)
2549 str = ftos(tm.(teamscores(ts_secondary)));
2550 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2553 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2558 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2561 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2564 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2565 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2566 else if(panel_bg_color_team > 0)
2567 panel_bg_color = rgb * panel_bg_color_team;
2569 panel_bg_color = rgb;
2570 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2572 panel_bg_color = panel_bg_color_save;
2576 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2577 if(tm.team != NUM_SPECTATOR)
2580 // display it anyway
2581 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2584 // draw scoreboard spectators before accuracy and item stats
2585 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2586 pos = Scoreboard_Spectators_Draw(pos);
2589 // draw accuracy and item stats
2590 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2591 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2592 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2593 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2595 // draw scoreboard spectators after accuracy and item stats and before rankings
2596 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2597 pos = Scoreboard_Spectators_Draw(pos);
2600 if(MUTATOR_CALLHOOK(ShowRankings)) {
2601 string ranktitle = M_ARGV(0, string);
2602 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2603 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2604 if(race_speedaward_alltimebest)
2607 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2611 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2612 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2613 str = strcat(str, " / ");
2615 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2616 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2617 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2618 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2620 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2625 // draw scoreboard spectators after rankings
2626 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2627 pos = Scoreboard_Spectators_Draw(pos);
2630 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2632 // draw scoreboard spectators after mapstats
2633 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2634 pos = Scoreboard_Spectators_Draw(pos);
2638 // print information about respawn status
2639 float respawn_time = STAT(RESPAWN_TIME);
2640 if(!intermission && respawn_time)
2642 if(respawn_time < 0)
2644 // a negative number means we are awaiting respawn, time value is still the same
2645 respawn_time *= -1; // remove mark now that we checked it
2647 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2648 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2650 str = sprintf(_("^1Respawning in ^3%s^1..."),
2651 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2652 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2654 count_seconds(ceil(respawn_time - time))
2658 else if(time < respawn_time)
2660 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2661 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2662 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2664 count_seconds(ceil(respawn_time - time))
2668 else if(time >= respawn_time)
2669 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2671 pos.y += 1.2 * hud_fontsize.y;
2672 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2675 pos.y += hud_fontsize.y;
2676 if (scoreboard_fade_alpha < 1)
2677 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2678 else if (pos.y != scoreboard_bottom)
2680 if (pos.y > scoreboard_bottom)
2681 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2683 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2688 if (scoreboard_fade_alpha == 1)
2690 if (scoreboard_bottom > 0.95 * vid_conheight)
2691 rankings_rows = max(1, rankings_rows - 1);
2692 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2693 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2695 rankings_cnt = rankings_rows * rankings_columns;