1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 float autocvar_hud_panel_scoreboard_spectators_position = 1;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_itemstats = true;
93 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
94 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
95 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
96 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
99 bool autocvar_hud_panel_scoreboard_dynamichud = false;
101 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
102 bool autocvar_hud_panel_scoreboard_others_showscore = true;
103 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
104 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
105 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
106 bool autocvar_hud_panel_scoreboard_playerid = false;
107 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
108 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
109 bool autocvar_hud_panel_scoreboard_scores_per_round;
111 float scoreboard_time;
115 if(autocvar_hud_panel_scoreboard_scores_per_round)
116 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
119 // mode 0: returns translated label
120 // mode 1: prints name and description of all the labels
121 string Label_getInfo(string label, int mode)
124 label = "bckills"; // first case in the switch
126 #define SCO_LABEL(strlabel, label, padding, help) \
129 return CTX(strlabel); \
130 LOG_HELP("^3", label, padding, "^7", help);
134 SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
135 SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
136 SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
137 SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
138 SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
139 SCO_LABEL(_("SCO^destroyed"), "destroyed", " ", _("Number of keys destroyed by pushing them into void"));
140 SCO_LABEL(_("SCO^damage"), "dmg", " ", _("The total damage done"));
141 SCO_LABEL(_("SCO^dmgtaken"), "dmgtaken", " ", _("The total damage taken"));
142 SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
143 SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
144 SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
145 SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
146 SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
147 SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
148 SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
149 SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
150 SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
151 SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
152 SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
153 SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
154 SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
155 SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
156 SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
157 SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
158 SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
159 SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
160 SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
161 SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
162 SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
163 SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
164 SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
165 SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
166 SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
167 SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
168 SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
169 SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
170 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
171 SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
172 SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
173 SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
174 SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
175 SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
176 SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
177 SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
178 SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
184 bool scoreboard_ui_disabling;
185 void HUD_Scoreboard_UI_Disable()
187 scoreboard_ui_disabling = true;
188 sb_showscores = false;
191 void HUD_Scoreboard_UI_Disable_Instantly()
193 scoreboard_ui_disabling = false;
194 scoreboard_ui_enabled = 0;
195 scoreboard_selected_panel = 0;
196 scoreboard_selected_player = NULL;
197 scoreboard_selected_team = NULL;
200 // mode: 0 normal, 1 team selection
201 void Scoreboard_UI_Enable(int mode)
207 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
210 // release player's pressed keys as they aren't released elsewhere
211 // in particular jump needs to be released as it may open the team selection
212 // (when server detects jump has been pressed it sends the command to open the team selection)
213 Release_Common_Keys();
214 scoreboard_ui_enabled = 2;
215 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
219 if (scoreboard_ui_enabled == 1)
221 scoreboard_ui_enabled = 1;
222 scoreboard_selected_panel = SB_PANEL_FIRST;
224 scoreboard_selected_player = NULL;
225 scoreboard_selected_team = NULL;
226 scoreboard_selected_panel_time = time;
229 int rankings_start_column;
230 int rankings_rows = 0;
231 int rankings_columns = 0;
232 int rankings_cnt = 0;
233 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
237 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
242 mousepos.x = nPrimary;
243 mousepos.y = nSecondary;
250 // at this point bInputType can only be 0 or 1 (key pressed or released)
251 bool key_pressed = (bInputType == 0);
253 // ESC to exit (TAB-ESC works too)
254 if(nPrimary == K_ESCAPE)
258 HUD_Scoreboard_UI_Disable();
262 // block any input while a menu dialog is fading
263 if(autocvar__menu_alpha)
269 // allow console bind to work
270 string con_keys = findkeysforcommand("toggleconsole", 0);
271 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
273 bool hit_con_bind = false;
275 for (i = 0; i < keys; ++i)
277 if(nPrimary == stof(argv(i)))
282 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
283 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
284 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
285 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
288 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
289 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
290 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
291 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
294 if(nPrimary == K_TAB)
298 if (scoreboard_ui_enabled == 2)
300 if (hudShiftState & S_SHIFT)
303 goto downarrow_action;
306 if (hudShiftState & S_SHIFT)
308 --scoreboard_selected_panel;
309 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
310 --scoreboard_selected_panel;
311 if (scoreboard_selected_panel < SB_PANEL_FIRST)
312 scoreboard_selected_panel = SB_PANEL_MAX;
316 ++scoreboard_selected_panel;
317 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
318 ++scoreboard_selected_panel;
319 if (scoreboard_selected_panel > SB_PANEL_MAX)
320 scoreboard_selected_panel = SB_PANEL_FIRST;
323 scoreboard_selected_panel_time = time;
325 else if(nPrimary == K_DOWNARROW)
329 LABEL(downarrow_action);
330 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
332 if (scoreboard_ui_enabled == 2)
334 entity curr_team = NULL;
335 bool scoreboard_selected_team_found = false;
336 if (!scoreboard_selected_team)
337 scoreboard_selected_team_found = true;
339 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
341 if(tm.team == NUM_SPECTATOR)
344 if (scoreboard_selected_team_found)
346 if (scoreboard_selected_team == tm)
347 scoreboard_selected_team_found = true;
350 if (curr_team == scoreboard_selected_team) // loop reached the last team
352 scoreboard_selected_team = curr_team;
357 entity curr_pl = NULL;
358 bool scoreboard_selected_player_found = false;
359 if (!scoreboard_selected_player)
360 scoreboard_selected_player_found = true;
362 for(tm = teams.sort_next; tm; tm = tm.sort_next)
364 if(tm.team != NUM_SPECTATOR)
365 for(pl = players.sort_next; pl; pl = pl.sort_next)
367 if(pl.team != tm.team)
370 if (scoreboard_selected_player_found)
372 if (scoreboard_selected_player == pl)
373 scoreboard_selected_player_found = true;
377 if (curr_pl == scoreboard_selected_player) // loop reached the last player
379 scoreboard_selected_player = curr_pl;
383 else if(nPrimary == K_UPARROW)
387 LABEL(uparrow_action);
388 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
390 if (scoreboard_ui_enabled == 2)
392 entity prev_team = NULL;
393 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
395 if(tm.team == NUM_SPECTATOR)
397 if (tm == scoreboard_selected_team)
402 scoreboard_selected_team = prev_team;
406 entity prev_pl = NULL;
408 for(tm = teams.sort_next; tm; tm = tm.sort_next)
410 if(tm.team != NUM_SPECTATOR)
411 for(pl = players.sort_next; pl; pl = pl.sort_next)
413 if(pl.team != tm.team)
415 if (pl == scoreboard_selected_player)
421 scoreboard_selected_player = prev_pl;
425 else if(nPrimary == K_RIGHTARROW)
429 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
430 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
432 else if(nPrimary == K_LEFTARROW)
436 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
437 rankings_start_column = max(rankings_start_column - 1, 0);
439 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
443 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
445 if (scoreboard_ui_enabled == 2)
448 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
451 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
452 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
453 HUD_Scoreboard_UI_Disable();
455 else if (scoreboard_selected_player)
456 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
459 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
463 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
465 switch (scoreboard_selected_columns_layout)
468 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
470 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
471 scoreboard_selected_columns_layout = 1;
476 localcmd(sprintf("scoreboard_columns_set default\n"));
477 scoreboard_selected_columns_layout = 2;
480 localcmd(sprintf("scoreboard_columns_set all\n"));
481 scoreboard_selected_columns_layout = 0;
486 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
490 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
491 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
493 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
497 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
499 if (scoreboard_selected_player)
501 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
502 HUD_Scoreboard_UI_Disable();
506 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
510 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
512 if (scoreboard_selected_player)
513 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
516 else if(hit_con_bind || nPrimary == K_PAUSE)
522 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
523 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
525 void Scoreboard_InitScores()
529 ps_primary = ps_secondary = NULL;
530 ts_primary = ts_secondary = -1;
531 FOREACH(Scores, true, {
532 if(scores_flags(it) & SFL_NOT_SORTABLE)
534 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
535 if(f == SFL_SORT_PRIO_PRIMARY)
537 if(f == SFL_SORT_PRIO_SECONDARY)
540 if(ps_secondary == NULL)
541 ps_secondary = ps_primary;
543 for(i = 0; i < MAX_TEAMSCORE; ++i)
545 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
546 if(f == SFL_SORT_PRIO_PRIMARY)
548 if(f == SFL_SORT_PRIO_SECONDARY)
551 if(ts_secondary == -1)
552 ts_secondary = ts_primary;
554 Cmd_Scoreboard_SetFields(0);
558 void Scoreboard_UpdatePlayerTeams()
560 static float update_time;
561 if (time <= update_time)
568 for(pl = players.sort_next; pl; pl = pl.sort_next)
570 numplayers += pl.team != NUM_SPECTATOR;
572 int Team = entcs_GetScoreTeam(pl.sv_entnum);
573 if(SetTeam(pl, Team))
576 Scoreboard_UpdatePlayerPos(pl);
580 pl = players.sort_next;
585 print(strcat("PNUM: ", ftos(num), "\n"));
590 int Scoreboard_CompareScore(int vl, int vr, int f)
592 TC(int, vl); TC(int, vr); TC(int, f);
593 if(f & SFL_ZERO_IS_WORST)
595 if(vl == 0 && vr != 0)
597 if(vl != 0 && vr == 0)
601 return IS_INCREASING(f);
603 return IS_DECREASING(f);
607 float Scoreboard_ComparePlayerScores(entity left, entity right)
609 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
610 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
617 if(vl == NUM_SPECTATOR)
619 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
621 if(!left.gotscores && right.gotscores)
626 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
627 if (res >= 0) return res;
629 if (ps_secondary && ps_secondary != ps_primary)
631 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
632 if (res >= 0) return res;
635 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
636 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
637 if (res >= 0) return res;
640 if (left.sv_entnum < right.sv_entnum)
646 void Scoreboard_UpdatePlayerPos(entity player)
649 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
651 SORT_SWAP(player, ent);
653 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
655 SORT_SWAP(ent, player);
659 float Scoreboard_CompareTeamScores(entity left, entity right)
661 if(left.team == NUM_SPECTATOR)
663 if(right.team == NUM_SPECTATOR)
668 for(int i = -2; i < MAX_TEAMSCORE; ++i)
672 if (fld_idx == -1) fld_idx = ts_primary;
673 else if (ts_secondary == ts_primary) continue;
674 else fld_idx = ts_secondary;
679 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
682 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
683 if (r >= 0) return r;
686 if (left.team < right.team)
692 void Scoreboard_UpdateTeamPos(entity Team)
695 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
697 SORT_SWAP(Team, ent);
699 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
701 SORT_SWAP(ent, Team);
705 void Cmd_Scoreboard_Help()
707 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
708 LOG_HELP(_("Usage:"));
709 LOG_HELP("^2scoreboard_columns_set ^3default");
710 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
711 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
712 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
713 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
714 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
715 LOG_HELP(_("The following field names are recognized (case insensitive):"));
721 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
722 "of game types, then a slash, to make the field show up only in these\n"
723 "or in all but these game types. You can also specify 'all' as a\n"
724 "field to show all fields available for the current game mode."));
727 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
728 "include/exclude ALL teams/noteams game modes."));
731 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
732 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
733 "right of the vertical bar aligned to the right."));
734 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
735 "other gamemodes except DM."));
738 // NOTE: adding a gametype with ? to not warn for an optional field
739 // make sure it's excluded in a previous exclusive rule, if any
740 // otherwise the previous exclusive rule warns anyway
741 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
742 #define SCOREBOARD_DEFAULT_COLUMNS \
743 "ping pl fps name |" \
744 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
745 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
747 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
748 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
749 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
750 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
751 " +surv/survivals +surv/hunts" \
752 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
753 " +lms/lives +lms/rank" \
754 " +kh/kckills +kh/losses +kh/caps" \
755 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
756 " +as/objectives +nb/faults +nb/goals" \
757 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
758 " +dom/ticks +dom/takes" \
759 " -lms,rc,cts,inv,nb/score"
761 void Cmd_Scoreboard_SetFields(int argc)
766 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
770 return; // do nothing, we don't know gametype and scores yet
772 // sbt_fields uses strunzone on the titles!
773 if(!sbt_field_title[0])
774 for(i = 0; i < MAX_SBT_FIELDS; ++i)
775 sbt_field_title[i] = strzone("(null)");
777 // TODO: re enable with gametype dependant cvars?
778 if(argc < 3) // no arguments provided
779 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
782 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
786 if(argv(2) == "default" || argv(2) == "expand_default")
788 if(argv(2) == "expand_default")
789 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
790 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
792 else if(argv(2) == "all" || argv(2) == "ALL")
794 string s = "ping pl name |"; // scores without label (not really scores)
797 // scores without label
798 s = strcat(s, " ", "sum");
799 s = strcat(s, " ", "kdratio");
800 s = strcat(s, " ", "frags");
802 FOREACH(Scores, true, {
804 if(it != ps_secondary)
805 if(scores_label(it) != "")
806 s = strcat(s, " ", scores_label(it));
808 if(ps_secondary != ps_primary)
809 s = strcat(s, " ", scores_label(ps_secondary));
810 s = strcat(s, " ", scores_label(ps_primary));
811 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
818 hud_fontsize = HUD_GetFontsize("hud_fontsize");
820 for(i = 1; i < argc - 1; ++i)
823 bool nocomplain = false;
824 if(substring(str, 0, 1) == "?")
827 str = substring(str, 1, strlen(str) - 1);
830 slash = strstrofs(str, "/", 0);
833 pattern = substring(str, 0, slash);
834 str = substring(str, slash + 1, strlen(str) - (slash + 1));
836 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
840 str = strtolower(str);
841 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
842 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
847 // fields without a label (not networked via the score system)
848 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
849 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
850 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
851 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
852 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
853 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
854 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
855 default: // fields with a label
857 // map alternative labels
858 if (str == "damage") str = "dmg";
859 if (str == "damagetaken") str = "dmgtaken";
861 FOREACH(Scores, true, {
862 if (str == strtolower(scores_label(it))) {
864 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
868 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
869 if(!nocomplain && str != "fps") // server can disable the fps field
870 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
872 strfree(sbt_field_title[sbt_num_fields]);
873 sbt_field_size[sbt_num_fields] = 0;
877 sbt_field[sbt_num_fields] = j;
880 if(j == ps_secondary)
881 have_secondary = true;
886 if(sbt_num_fields >= MAX_SBT_FIELDS)
890 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
892 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
893 have_secondary = true;
894 if(ps_primary == ps_secondary)
895 have_secondary = true;
896 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
898 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
902 strfree(sbt_field_title[sbt_num_fields]);
903 for(i = sbt_num_fields; i > 0; --i)
905 sbt_field_title[i] = sbt_field_title[i-1];
906 sbt_field_size[i] = sbt_field_size[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_size[i] = sbt_field_size[i-1];
921 sbt_field[i] = sbt_field[i-1];
923 sbt_field_title[1] = strzone("|");
924 sbt_field[1] = SP_SEPARATOR;
925 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
927 LOG_INFO("fixed missing field '|'");
930 else if(!have_separator)
932 strcpy(sbt_field_title[sbt_num_fields], "|");
933 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
934 sbt_field[sbt_num_fields] = SP_SEPARATOR;
936 LOG_INFO("fixed missing field '|'");
940 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
941 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
942 sbt_field[sbt_num_fields] = ps_secondary;
944 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
948 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
949 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
950 sbt_field[sbt_num_fields] = ps_primary;
952 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
956 sbt_field[sbt_num_fields] = SP_END;
959 string Scoreboard_AddPlayerId(string pl_name, entity pl)
961 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
962 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
963 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
967 vector sbt_field_rgb;
968 string sbt_field_icon0;
969 string sbt_field_icon1;
970 string sbt_field_icon2;
971 vector sbt_field_icon0_rgb;
972 vector sbt_field_icon1_rgb;
973 vector sbt_field_icon2_rgb;
974 string Scoreboard_GetName(entity pl)
976 if(ready_waiting && pl.ready)
978 sbt_field_icon0 = "gfx/scoreboard/player_ready";
983 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
984 if (playerslots[pl.sv_entnum].colormap >= 1024)
985 f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
987 f = entcs_GetClientColors(pl.sv_entnum);
990 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
991 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
992 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
993 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
994 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
997 return entcs_GetName(pl.sv_entnum);
1000 int autocvar_hud_panel_scoreboard_ping_best = 0;
1001 int autocvar_hud_panel_scoreboard_ping_medium = 70;
1002 int autocvar_hud_panel_scoreboard_ping_high = 100;
1003 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1004 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1005 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1006 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1007 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1008 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1009 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1010 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1011 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1012 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1013 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1014 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1015 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1016 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1018 float tmp, num, denom;
1021 sbt_field_rgb = '1 1 1';
1022 sbt_field_icon0 = "";
1023 sbt_field_icon1 = "";
1024 sbt_field_icon2 = "";
1025 sbt_field_icon0_rgb = '1 1 1';
1026 sbt_field_icon1_rgb = '1 1 1';
1027 sbt_field_icon2_rgb = '1 1 1';
1028 int rounds_played = 0;
1030 rounds_played = pl.(scores(SP_ROUNDS_PL));
1035 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1036 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1041 sbt_field_rgb = COLOR_BEST;
1042 else if(f < PING_MED)
1043 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1044 else if(f < PING_HIGH)
1045 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1046 else if(f < PING_WORST)
1047 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1049 sbt_field_rgb = COLOR_WORST;
1055 f = pl.ping_packetloss;
1056 tmp = pl.ping_movementloss;
1057 if(f == 0 && tmp == 0)
1059 str = ftos(ceil(f * 100));
1061 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1062 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1063 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1067 str = Scoreboard_GetName(pl);
1068 if (autocvar_hud_panel_scoreboard_playerid)
1069 str = Scoreboard_AddPlayerId(str, pl);
1073 f = pl.(scores(SP_KILLS));
1074 f -= pl.(scores(SP_SUICIDES));
1076 return sprintf("%.1f", f / rounds_played);
1080 num = pl.(scores(SP_KILLS));
1081 denom = pl.(scores(SP_DEATHS));
1084 sbt_field_rgb = '0 1 0';
1086 str = sprintf("%.1f", num / rounds_played);
1088 str = sprintf("%d", num);
1089 } else if(num <= 0) {
1090 sbt_field_rgb = '1 0 0';
1092 str = sprintf("%.2f", num / (denom * rounds_played));
1094 str = sprintf("%.1f", num / denom);
1098 str = sprintf("%.2f", num / (denom * rounds_played));
1100 str = sprintf("%.1f", num / denom);
1105 f = pl.(scores(SP_KILLS));
1106 f -= pl.(scores(SP_DEATHS));
1109 sbt_field_rgb = '0 1 0';
1111 sbt_field_rgb = '1 1 1';
1113 sbt_field_rgb = '1 0 0';
1116 return sprintf("%.1f", f / rounds_played);
1121 float elo = pl.(scores(SP_ELO));
1123 case -1: return "...";
1124 case -2: return _("N/A");
1125 default: return ftos(elo);
1131 float fps = pl.(scores(SP_FPS));
1134 sbt_field_rgb = '1 1 1';
1135 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1137 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1138 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1143 return ftos(pl.(scores(field)));
1145 case SP_DMG: case SP_DMGTAKEN:
1147 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1148 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1150 default: case SP_SCORE:
1151 tmp = pl.(scores(field));
1152 f = scores_flags(field);
1153 if(field == ps_primary)
1154 sbt_field_rgb = '1 1 0';
1155 else if(field == ps_secondary)
1156 sbt_field_rgb = '0 1 1';
1158 sbt_field_rgb = '1 1 1';
1159 return ScoreString(f, tmp, rounds_played);
1164 float sbt_fixcolumnwidth_len;
1165 float sbt_fixcolumnwidth_iconlen;
1166 float sbt_fixcolumnwidth_marginlen;
1168 string Scoreboard_FixColumnWidth(int i, string str)
1174 sbt_fixcolumnwidth_iconlen = 0;
1176 if(sbt_field_icon0 != "")
1178 sz = draw_getimagesize(sbt_field_icon0);
1180 if(sbt_fixcolumnwidth_iconlen < f)
1181 sbt_fixcolumnwidth_iconlen = f;
1184 if(sbt_field_icon1 != "")
1186 sz = draw_getimagesize(sbt_field_icon1);
1188 if(sbt_fixcolumnwidth_iconlen < f)
1189 sbt_fixcolumnwidth_iconlen = f;
1192 if(sbt_field_icon2 != "")
1194 sz = draw_getimagesize(sbt_field_icon2);
1196 if(sbt_fixcolumnwidth_iconlen < f)
1197 sbt_fixcolumnwidth_iconlen = f;
1200 if(sbt_fixcolumnwidth_iconlen != 0)
1202 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1203 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1206 sbt_fixcolumnwidth_marginlen = 0;
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] = 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 = 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 = vid_conwidth - remaining_space;
1227 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1229 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1230 if(sbt_field_size[i] < f)
1231 sbt_field_size[i] = f;
1236 void Scoreboard_initFieldSizes()
1238 for(int i = 0; i < sbt_num_fields; ++i)
1240 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1241 Scoreboard_FixColumnWidth(i, "");
1245 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1248 vector column_dim = eY * panel_size.y;
1250 column_dim.y -= 1.25 * hud_fontsize.y;
1251 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1252 pos.x += hud_fontsize.x * 0.5;
1253 for(i = 0; i < sbt_num_fields; ++i)
1255 if(sbt_field[i] == SP_SEPARATOR)
1257 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1260 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1261 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1262 pos.x += column_dim.x;
1264 if(sbt_field[i] == SP_SEPARATOR)
1266 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1267 for(i = sbt_num_fields - 1; i > 0; --i)
1269 if(sbt_field[i] == SP_SEPARATOR)
1272 pos.x -= sbt_field_size[i];
1277 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1278 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1281 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1282 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1283 pos.x -= hud_fontsize.x;
1287 pos.x = panel_pos.x;
1288 pos.y += 1.25 * hud_fontsize.y;
1292 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1294 TC(bool, is_self); TC(int, pl_number);
1296 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1298 vector h_pos = item_pos;
1299 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1300 // alternated rows highlighting
1301 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1303 if (pl == scoreboard_selected_player)
1304 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1307 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1308 else if((sbt_highlight) && (!(pl_number % 2)))
1309 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1311 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1313 vector pos = item_pos;
1314 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1316 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1318 pos.x += hud_fontsize.x * 0.5;
1319 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1320 vector tmp = '0 0 0';
1322 PlayerScoreField field;
1323 for(i = 0; i < sbt_num_fields; ++i)
1325 field = sbt_field[i];
1326 if(field == SP_SEPARATOR)
1329 if(is_spec && field != SP_NAME && field != SP_PING) {
1330 pos.x += sbt_field_size[i] + hud_fontsize.x;
1333 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1334 str = Scoreboard_FixColumnWidth(i, str);
1336 pos.x += sbt_field_size[i] + hud_fontsize.x;
1338 if(field == SP_NAME) {
1339 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1340 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1342 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1343 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1346 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1347 if(sbt_field_icon0 != "")
1348 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1349 if(sbt_field_icon1 != "")
1350 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1351 if(sbt_field_icon2 != "")
1352 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1355 if(sbt_field[i] == SP_SEPARATOR)
1357 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1358 for(i = sbt_num_fields-1; i > 0; --i)
1360 field = sbt_field[i];
1361 if(field == SP_SEPARATOR)
1364 if(is_spec && field != SP_NAME && field != SP_PING) {
1365 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1369 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1370 str = Scoreboard_FixColumnWidth(i, str);
1372 if(field == SP_NAME) {
1373 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1374 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1376 tmp.x = sbt_fixcolumnwidth_len;
1377 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1380 tmp.x = sbt_field_size[i];
1381 if(sbt_field_icon0 != "")
1382 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1383 if(sbt_field_icon1 != "")
1384 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1385 if(sbt_field_icon2 != "")
1386 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1387 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1392 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1395 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1398 vector h_pos = item_pos;
1399 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1401 bool complete = (this_team == NUM_SPECTATOR);
1404 if((sbt_highlight) && (!(pl_number % 2)))
1405 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1407 vector pos = item_pos;
1408 pos.x += hud_fontsize.x * 0.5;
1409 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1411 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1413 width_limit -= stringwidth("...", false, hud_fontsize);
1414 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1415 static float max_name_width = 0;
1417 float fieldsize = 0;
1418 float min_fieldsize = 0;
1419 float fieldpadding = hud_fontsize.x * 0.25;
1420 if(this_team == NUM_SPECTATOR)
1422 if(autocvar_hud_panel_scoreboard_spectators_showping)
1423 min_fieldsize = stringwidth("999", false, hud_fontsize);
1425 else if(autocvar_hud_panel_scoreboard_others_showscore)
1426 min_fieldsize = stringwidth("99", false, hud_fontsize);
1427 for(i = 0; pl; pl = pl.sort_next)
1429 if(pl.team != this_team)
1431 if(pl == ignored_pl)
1434 if(entcs_GetWantsJoin(pl.sv_entnum))
1436 vector tmcolor = Team_ColorRGB(Team_IndexToTeam(entcs_GetWantsJoin(pl.sv_entnum)));
1437 tmcolor -= tmcolor * sin(2*M_PI*time);
1439 drawstring(pos, "(Q)", hud_fontsize, tmcolor, sbt_fg_alpha, DRAWFLAG_NORMAL);
1440 pos.x += stringwidth("(Q) ", true, hud_fontsize);
1444 if(this_team == NUM_SPECTATOR)
1446 if(autocvar_hud_panel_scoreboard_spectators_showping)
1447 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1449 else if(autocvar_hud_panel_scoreboard_others_showscore)
1450 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1452 string str = entcs_GetName(pl.sv_entnum);
1453 if (autocvar_hud_panel_scoreboard_playerid)
1454 str = Scoreboard_AddPlayerId(str, pl);
1455 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1456 float column_width = stringwidth(str, true, hud_fontsize);
1457 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1459 if(column_width > max_name_width)
1460 max_name_width = column_width;
1461 column_width = max_name_width;
1465 fieldsize = stringwidth(field, false, hud_fontsize);
1466 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1469 if(pos.x + column_width > width_limit)
1474 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1479 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1480 pos.y += hud_fontsize.y * 1.25;
1484 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1486 if (pl == scoreboard_selected_player)
1488 h_size.x = column_width + hud_fontsize.x * 0.25;
1489 h_size.y = hud_fontsize.y;
1490 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1494 vector name_pos = pos;
1495 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1496 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1497 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1500 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1501 h_size.y = hud_fontsize.y;
1502 vector field_pos = pos;
1503 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1504 field_pos.x += column_width - h_size.x;
1506 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1507 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1508 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1512 h_size.x = column_width + hud_fontsize.x * 0.25;
1513 h_size.y = hud_fontsize.y;
1514 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1516 pos.x += column_width;
1517 pos.x += hud_fontsize.x;
1519 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1522 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1524 int max_players = 999;
1525 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1527 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1530 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1531 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1532 height /= team_count;
1535 height -= panel_bg_padding * 2; // - padding
1536 max_players = floor(height / (hud_fontsize.y * 1.25));
1537 if(max_players <= 1)
1539 if(max_players == tm.team_size)
1544 entity me = playerslots[current_player];
1546 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1547 panel_size.y += panel_bg_padding * 2;
1549 vector scoreboard_selected_hl_pos = pos;
1550 vector scoreboard_selected_hl_size = '0 0 0';
1551 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1552 scoreboard_selected_hl_size.y = panel_size.y;
1556 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1557 if(panel.current_panel_bg != "0")
1558 end_pos.y += panel_bg_border * 2;
1560 if(panel_bg_padding)
1562 panel_pos += '1 1 0' * panel_bg_padding;
1563 panel_size -= '2 2 0' * panel_bg_padding;
1567 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1571 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1573 pos.y += 1.25 * hud_fontsize.y;
1576 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1578 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1581 // print header row and highlight columns
1582 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1584 // fill the table and draw the rows
1585 bool is_self = false;
1586 bool self_shown = false;
1588 for(pl = players.sort_next; pl; pl = pl.sort_next)
1590 if(pl.team != tm.team)
1592 if(i == max_players - 2 && pl != me)
1594 if(!self_shown && me.team == tm.team)
1596 Scoreboard_DrawItem(pos, rgb, me, true, i);
1598 pos.y += 1.25 * hud_fontsize.y;
1602 if(i >= max_players - 1)
1604 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1607 is_self = (pl.sv_entnum == current_player);
1608 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1611 pos.y += 1.25 * hud_fontsize.y;
1615 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1617 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1619 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1620 _alpha *= panel_fg_alpha;
1622 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1626 panel_size.x += panel_bg_padding * 2; // restore initial width
1630 bool Scoreboard_WouldDraw()
1632 if (scoreboard_ui_enabled)
1634 if (scoreboard_ui_disabling)
1636 if (scoreboard_fade_alpha == 0)
1637 HUD_Scoreboard_UI_Disable_Instantly();
1640 if (intermission && scoreboard_ui_enabled == 2)
1642 HUD_Scoreboard_UI_Disable_Instantly();
1647 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1649 else if (QuickMenu_IsOpened())
1651 else if (HUD_Radar_Clickable())
1653 else if (sb_showscores) // set by +showscores engine command
1655 else if (intermission == 1)
1657 else if (intermission == 2)
1659 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1660 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1664 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1669 float average_accuracy;
1670 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1672 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1674 WepSet weapons_stat = WepSet_GetFromStat();
1675 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1676 int disownedcnt = 0;
1678 FOREACH(Weapons, it != WEP_Null, {
1679 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1681 WepSet set = it.m_wepset;
1682 if(it.spawnflags & WEP_TYPE_OTHER)
1687 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1689 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1696 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1697 if (weapon_cnt <= 0) return pos;
1700 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1702 int columns = ceil(weapon_cnt / rows);
1704 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1705 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1706 float height = weapon_height + hud_fontsize.y;
1708 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);
1709 pos.y += 1.25 * hud_fontsize.y;
1710 if(panel.current_panel_bg != "0")
1711 pos.y += panel_bg_border;
1714 panel_size.y = height * rows;
1715 panel_size.y += panel_bg_padding * 2;
1717 float panel_bg_alpha_save = panel_bg_alpha;
1718 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1720 panel_bg_alpha = panel_bg_alpha_save;
1722 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1723 if(panel.current_panel_bg != "0")
1724 end_pos.y += panel_bg_border * 2;
1726 if(panel_bg_padding)
1728 panel_pos += '1 1 0' * panel_bg_padding;
1729 panel_size -= '2 2 0' * panel_bg_padding;
1733 vector tmp = panel_size;
1735 float weapon_width = tmp.x / columns / rows;
1738 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1742 // column highlighting
1743 for (int i = 0; i < columns; ++i)
1745 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);
1748 for (int i = 0; i < rows; ++i)
1749 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1752 average_accuracy = 0;
1753 int weapons_with_stats = 0;
1755 pos.x += weapon_width / 2;
1757 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1760 Accuracy_LoadColors();
1762 float oldposx = pos.x;
1766 FOREACH(Weapons, it != WEP_Null, {
1767 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1769 WepSet set = it.m_wepset;
1770 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1772 if (it.spawnflags & WEP_TYPE_OTHER)
1776 if (weapon_stats >= 0)
1777 weapon_alpha = sbt_fg_alpha;
1779 weapon_alpha = 0.2 * sbt_fg_alpha;
1782 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1784 if (weapon_stats >= 0) {
1785 weapons_with_stats += 1;
1786 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1788 string s = sprintf("%d%%", weapon_stats * 100);
1789 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1791 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1792 rgb = Accuracy_GetColor(weapon_stats);
1794 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1796 tmpos.x += weapon_width * rows;
1797 pos.x += weapon_width * rows;
1798 if (rows == 2 && column == columns - 1) {
1806 if (weapons_with_stats)
1807 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1809 panel_size.x += panel_bg_padding * 2; // restore initial width
1814 bool is_item_filtered(entity it)
1816 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1818 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1821 if (it.instanceOfArmor || it.instanceOfHealth)
1823 int ha_mask = floor(mask) % 10;
1826 default: return false;
1827 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1828 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1829 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1830 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1833 if (it.instanceOfAmmo)
1835 int ammo_mask = floor(mask / 10) % 10;
1836 return (ammo_mask == 1);
1841 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1843 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1845 int disowned_cnt = 0;
1846 int uninteresting_cnt = 0;
1847 IL_EACH(default_order_items, true, {
1848 int q = g_inventory.inv_items[it.m_id];
1849 //q = 1; // debug: display all items
1850 if (is_item_filtered(it))
1851 ++uninteresting_cnt;
1855 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1856 int n = items_cnt - disowned_cnt;
1857 if (n <= 0) return pos;
1859 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1860 int columns = max(6, ceil(n / rows));
1862 float item_height = hud_fontsize.y * 2.3;
1863 float height = item_height + hud_fontsize.y;
1865 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1866 pos.y += 1.25 * hud_fontsize.y;
1867 if(panel.current_panel_bg != "0")
1868 pos.y += panel_bg_border;
1871 panel_size.y = height * rows;
1872 panel_size.y += panel_bg_padding * 2;
1874 float panel_bg_alpha_save = panel_bg_alpha;
1875 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1877 panel_bg_alpha = panel_bg_alpha_save;
1879 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1880 if(panel.current_panel_bg != "0")
1881 end_pos.y += panel_bg_border * 2;
1883 if(panel_bg_padding)
1885 panel_pos += '1 1 0' * panel_bg_padding;
1886 panel_size -= '2 2 0' * panel_bg_padding;
1890 vector tmp = panel_size;
1892 float item_width = tmp.x / columns / rows;
1895 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1899 // column highlighting
1900 for (int i = 0; i < columns; ++i)
1902 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);
1905 for (int i = 0; i < rows; ++i)
1906 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1910 pos.x += item_width / 2;
1912 float oldposx = pos.x;
1916 IL_EACH(default_order_items, !is_item_filtered(it), {
1917 int n = g_inventory.inv_items[it.m_id];
1918 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1919 if (n <= 0) continue;
1920 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);
1922 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1923 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1924 tmpos.x += item_width * rows;
1925 pos.x += item_width * rows;
1926 if (rows == 2 && column == columns - 1) {
1934 panel_size.x += panel_bg_padding * 2; // restore initial width
1939 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1941 pos.x += hud_fontsize.x * 0.25;
1942 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1943 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1944 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1946 pos.y += hud_fontsize.y;
1951 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1952 float stat_secrets_found, stat_secrets_total;
1953 float stat_monsters_killed, stat_monsters_total;
1957 // get monster stats
1958 stat_monsters_killed = STAT(MONSTERS_KILLED);
1959 stat_monsters_total = STAT(MONSTERS_TOTAL);
1961 // get secrets stats
1962 stat_secrets_found = STAT(SECRETS_FOUND);
1963 stat_secrets_total = STAT(SECRETS_TOTAL);
1965 // get number of rows
1966 if(stat_secrets_total)
1968 if(stat_monsters_total)
1971 // if no rows, return
1975 // draw table header
1976 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1977 pos.y += 1.25 * hud_fontsize.y;
1978 if(panel.current_panel_bg != "0")
1979 pos.y += panel_bg_border;
1982 panel_size.y = hud_fontsize.y * rows;
1983 panel_size.y += panel_bg_padding * 2;
1986 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1987 if(panel.current_panel_bg != "0")
1988 end_pos.y += panel_bg_border * 2;
1990 if(panel_bg_padding)
1992 panel_pos += '1 1 0' * panel_bg_padding;
1993 panel_size -= '2 2 0' * panel_bg_padding;
1997 vector tmp = panel_size;
2000 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2003 if(stat_monsters_total)
2005 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2006 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2010 if(stat_secrets_total)
2012 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2013 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2016 panel_size.x += panel_bg_padding * 2; // restore initial width
2020 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2023 RANKINGS_RECEIVED_CNT = 0;
2024 for (i=RANKINGS_CNT-1; i>=0; --i)
2026 ++RANKINGS_RECEIVED_CNT;
2028 if (RANKINGS_RECEIVED_CNT == 0)
2031 vector hl_rgb = rgb + '0.5 0.5 0.5';
2033 vector scoreboard_selected_hl_pos = pos;
2035 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2036 pos.y += 1.25 * hud_fontsize.y;
2037 if(panel.current_panel_bg != "0")
2038 pos.y += panel_bg_border;
2040 vector scoreboard_selected_hl_size = '0 0 0';
2041 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2042 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2047 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2049 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2054 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2056 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2060 float ranksize = 3 * hud_fontsize.x;
2061 float timesize = 5 * hud_fontsize.x;
2062 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2063 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2064 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2067 rankings_cnt = RANKINGS_RECEIVED_CNT;
2068 rankings_rows = ceil(rankings_cnt / rankings_columns);
2071 // expand name column to fill the entire row
2072 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2073 namesize += available_space;
2074 columnsize.x += available_space;
2076 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2077 panel_size.y += panel_bg_padding * 2;
2078 scoreboard_selected_hl_size.y += panel_size.y;
2082 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2083 if(panel.current_panel_bg != "0")
2084 end_pos.y += panel_bg_border * 2;
2086 if(panel_bg_padding)
2088 panel_pos += '1 1 0' * panel_bg_padding;
2089 panel_size -= '2 2 0' * panel_bg_padding;
2095 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2097 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2099 int column = 0, j = 0;
2100 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2101 int start_item = rankings_start_column * rankings_rows;
2102 for(i = start_item; i < start_item + rankings_cnt; ++i)
2104 int t = grecordtime[i];
2108 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2109 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2110 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2111 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2113 str = count_ordinal(i+1);
2114 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2115 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2116 str = ColorTranslateRGB(grecordholder[i]);
2118 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2119 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2121 pos.y += 1.25 * hud_fontsize.y;
2123 if(j >= rankings_rows)
2127 pos.x += panel_size.x / rankings_columns;
2128 pos.y = panel_pos.y;
2131 strfree(zoned_name_self);
2133 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2135 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2136 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2139 panel_size.x += panel_bg_padding * 2; // restore initial width
2143 bool have_weapon_stats;
2144 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2146 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2148 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2151 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2152 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2158 if (!have_weapon_stats)
2160 FOREACH(Weapons, it != WEP_Null, {
2161 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2162 if (weapon_stats >= 0)
2164 have_weapon_stats = true;
2168 if (!have_weapon_stats)
2175 bool have_item_stats;
2176 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2178 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2180 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2183 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2184 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2190 if (!have_item_stats)
2192 IL_EACH(default_order_items, true, {
2193 if (!is_item_filtered(it))
2195 int q = g_inventory.inv_items[it.m_id];
2196 //q = 1; // debug: display all items
2199 have_item_stats = true;
2204 if (!have_item_stats)
2211 vector Scoreboard_Spectators_Draw(vector pos) {
2216 for(pl = players.sort_next; pl; pl = pl.sort_next)
2218 if(pl.team == NUM_SPECTATOR)
2220 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2221 if(tm.team == NUM_SPECTATOR)
2223 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2224 draw_beginBoldFont();
2225 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2227 pos.y += 1.25 * hud_fontsize.y;
2229 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2230 pos.y += 1.25 * hud_fontsize.y;
2235 if (str != "") // if there's at least one spectator
2236 pos.y += 0.5 * hud_fontsize.y;
2241 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2243 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2244 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2245 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2246 (s_label == "score") ? CTX(_("SCO^points")) :
2247 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2250 void Scoreboard_Draw()
2252 if(!autocvar__hud_configure)
2254 if(!hud_draw_maximized) return;
2256 // frametime checks allow to toggle the scoreboard even when the game is paused
2257 if(scoreboard_active) {
2258 if (scoreboard_fade_alpha == 0)
2259 scoreboard_time = time;
2260 if(hud_configure_menu_open == 1)
2261 scoreboard_fade_alpha = 1;
2262 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2263 if (scoreboard_fadeinspeed && frametime)
2264 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2266 scoreboard_fade_alpha = 1;
2267 if(hud_fontsize_str != autocvar_hud_fontsize)
2269 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2270 Scoreboard_initFieldSizes();
2271 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2275 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2276 if (scoreboard_fadeoutspeed && frametime)
2277 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2279 scoreboard_fade_alpha = 0;
2282 if (!scoreboard_fade_alpha)
2284 scoreboard_acc_fade_alpha = 0;
2285 scoreboard_itemstats_fade_alpha = 0;
2290 scoreboard_fade_alpha = 0;
2292 if (autocvar_hud_panel_scoreboard_dynamichud)
2295 HUD_Scale_Disable();
2297 if(scoreboard_fade_alpha <= 0)
2299 panel_fade_alpha *= scoreboard_fade_alpha;
2300 HUD_Panel_LoadCvars();
2302 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2303 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2304 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2305 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2306 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2307 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2308 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2310 // don't overlap with con_notify
2311 if(!autocvar__hud_configure)
2312 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2314 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2315 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2316 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2317 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2318 panel_pos.x = scoreboard_left;
2319 panel_size.x = fixed_scoreboard_width;
2321 Scoreboard_UpdatePlayerTeams();
2323 scoreboard_top = panel_pos.y;
2324 vector pos = panel_pos;
2329 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2331 // Begin of Game Info Section
2332 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2333 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2335 // Game Info: Game Type
2336 if (scoreboard_ui_enabled == 2)
2337 str = _("Team Selection");
2339 str = MapInfo_Type_ToText(gametype);
2340 draw_beginBoldFont();
2341 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);
2344 pos.y += sb_gameinfo_type_fontsize.y;
2345 // Game Info: Game Detail
2346 if (scoreboard_ui_enabled == 2)
2348 if (scoreboard_selected_team)
2349 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2351 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2352 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);
2354 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2355 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2356 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);
2360 float tl = STAT(TIMELIMIT);
2361 float fl = STAT(FRAGLIMIT);
2362 float ll = STAT(LEADLIMIT);
2363 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2366 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2367 if(!gametype.m_hidelimits)
2372 str = strcat(str, "^7 / "); // delimiter
2373 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2377 if(tl > 0 || fl > 0)
2380 if (ll_and_fl && fl > 0)
2381 str = strcat(str, "^7 & ");
2383 str = strcat(str, "^7 / ");
2385 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2388 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
2389 // map name and player count
2393 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2394 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2395 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2397 // End of Game Info Section
2399 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2400 if(panel.current_panel_bg != "0")
2401 pos.y += panel_bg_border;
2403 // Draw the scoreboard
2404 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2407 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2411 vector panel_bg_color_save = panel_bg_color;
2412 vector team_score_baseoffset;
2413 vector team_size_baseoffset;
2414 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2416 // put team score to the left of scoreboard (and team size to the right)
2417 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2418 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2419 if(panel.current_panel_bg != "0")
2421 team_score_baseoffset.x -= panel_bg_border;
2422 team_size_baseoffset.x += panel_bg_border;
2427 // put team score to the right of scoreboard (and team size to the left)
2428 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2429 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2430 if(panel.current_panel_bg != "0")
2432 team_score_baseoffset.x += panel_bg_border;
2433 team_size_baseoffset.x -= panel_bg_border;
2437 int team_size_total = 0;
2438 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2440 // calculate team size total (sum of all team sizes)
2441 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2442 if(tm.team != NUM_SPECTATOR)
2443 team_size_total += tm.team_size;
2446 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2448 if(tm.team == NUM_SPECTATOR)
2453 draw_beginBoldFont();
2454 vector rgb = Team_ColorRGB(tm.team);
2455 str = ftos(tm.(teamscores(ts_primary)));
2456 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2458 // team score on the left (default)
2459 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2463 // team score on the right
2464 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2466 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2468 // team size (if set to show on the side)
2469 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2471 // calculate the starting position for the whole team size info string
2472 str = sprintf("%d/%d", tm.team_size, team_size_total);
2473 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2475 // team size on the left
2476 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2480 // team size on the right
2481 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2483 str = sprintf("%d", tm.team_size);
2484 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2485 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2486 str = sprintf("/%d", team_size_total);
2487 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2491 // secondary score, e.g. keyhunt
2492 if(ts_primary != ts_secondary)
2494 str = ftos(tm.(teamscores(ts_secondary)));
2495 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2498 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2503 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2506 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2509 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2510 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2511 else if(panel_bg_color_team > 0)
2512 panel_bg_color = rgb * panel_bg_color_team;
2514 panel_bg_color = rgb;
2515 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2517 panel_bg_color = panel_bg_color_save;
2521 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2522 if(tm.team != NUM_SPECTATOR)
2525 // display it anyway
2526 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2529 // draw scoreboard spectators before accuracy and item stats
2530 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2531 pos = Scoreboard_Spectators_Draw(pos);
2534 // draw accuracy and item stats
2535 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2536 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2537 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2538 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2540 // draw scoreboard spectators after accuracy and item stats and before rankings
2541 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2542 pos = Scoreboard_Spectators_Draw(pos);
2545 if(MUTATOR_CALLHOOK(ShowRankings)) {
2546 string ranktitle = M_ARGV(0, string);
2547 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2548 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2549 if(race_speedaward_alltimebest)
2552 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2556 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2557 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2558 str = strcat(str, " / ");
2560 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2561 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2562 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2563 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2565 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2570 // draw scoreboard spectators after rankings
2571 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2572 pos = Scoreboard_Spectators_Draw(pos);
2575 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2577 // draw scoreboard spectators after mapstats
2578 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2579 pos = Scoreboard_Spectators_Draw(pos);
2583 // print information about respawn status
2584 float respawn_time = STAT(RESPAWN_TIME);
2585 if(!intermission && respawn_time)
2587 if(respawn_time < 0)
2589 // a negative number means we are awaiting respawn, time value is still the same
2590 respawn_time *= -1; // remove mark now that we checked it
2592 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2593 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2595 str = sprintf(_("^1Respawning in ^3%s^1..."),
2596 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2597 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2599 count_seconds(ceil(respawn_time - time))
2603 else if(time < respawn_time)
2605 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2606 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2607 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2609 count_seconds(ceil(respawn_time - time))
2613 else if(time >= respawn_time)
2614 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2616 pos.y += 1.2 * hud_fontsize.y;
2617 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2620 pos.y += hud_fontsize.y;
2621 if (scoreboard_fade_alpha < 1)
2622 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2623 else if (pos.y != scoreboard_bottom)
2625 if (pos.y > scoreboard_bottom)
2626 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2628 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2633 if (scoreboard_fade_alpha == 1)
2635 if (scoreboard_bottom > 0.95 * vid_conheight)
2636 rankings_rows = max(1, rankings_rows - 1);
2637 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2638 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2640 rankings_cnt = rankings_rows * rankings_columns;