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
128 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
129 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
130 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
131 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
132 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
133 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
134 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
135 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
136 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
137 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
138 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
139 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
140 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
141 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
142 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
143 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
144 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
145 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
146 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
147 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
148 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
149 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
150 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
151 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
152 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
153 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
154 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
155 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
156 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
157 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
158 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
159 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
160 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
161 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
162 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
163 case "rounds_pl": if (!mode) return CTX(_("SCO^rounds played"));else LOG_HELP(strcat("^3", "rounds_pl", " ^7", _("Number of rounds played")));
164 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
165 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
166 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
167 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
168 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
169 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
170 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
171 default: return label;
176 bool scoreboard_ui_disabling;
177 void HUD_Scoreboard_UI_Disable()
179 scoreboard_ui_disabling = true;
180 sb_showscores = false;
183 void HUD_Scoreboard_UI_Disable_Instantly()
185 scoreboard_ui_disabling = false;
186 scoreboard_ui_enabled = 0;
187 scoreboard_selected_panel = 0;
188 scoreboard_selected_player = NULL;
189 scoreboard_selected_team = NULL;
192 // mode: 0 normal, 1 team selection
193 void Scoreboard_UI_Enable(int mode)
199 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
202 // release player's pressed keys as they aren't released elsewhere
203 // in particular jump needs to be released as it may open the team selection
204 // (when server detects jump has been pressed it sends the command to open the team selection)
205 Release_Common_Keys();
206 scoreboard_ui_enabled = 2;
207 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
211 if (scoreboard_ui_enabled == 1)
213 scoreboard_ui_enabled = 1;
214 scoreboard_selected_panel = SB_PANEL_FIRST;
216 scoreboard_selected_player = NULL;
217 scoreboard_selected_team = NULL;
218 scoreboard_selected_panel_time = time;
221 int rankings_start_column;
222 int rankings_rows = 0;
223 int rankings_columns = 0;
224 int rankings_cnt = 0;
225 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
229 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
234 mousepos.x = nPrimary;
235 mousepos.y = nSecondary;
242 // at this point bInputType can only be 0 or 1 (key pressed or released)
243 bool key_pressed = (bInputType == 0);
245 // ESC to exit (TAB-ESC works too)
246 if(nPrimary == K_ESCAPE)
250 HUD_Scoreboard_UI_Disable();
254 // block any input while a menu dialog is fading
255 if(autocvar__menu_alpha)
261 // allow console bind to work
262 string con_keys = findkeysforcommand("toggleconsole", 0);
263 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
265 bool hit_con_bind = false;
267 for (i = 0; i < keys; ++i)
269 if(nPrimary == stof(argv(i)))
274 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
275 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
276 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
277 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
280 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
281 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
282 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
283 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
286 if(nPrimary == K_TAB)
290 if (scoreboard_ui_enabled == 2)
292 if (hudShiftState & S_SHIFT)
295 goto downarrow_action;
298 if (hudShiftState & S_SHIFT)
300 --scoreboard_selected_panel;
301 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
302 --scoreboard_selected_panel;
303 if (scoreboard_selected_panel < SB_PANEL_FIRST)
304 scoreboard_selected_panel = SB_PANEL_MAX;
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_MAX)
312 scoreboard_selected_panel = SB_PANEL_FIRST;
315 scoreboard_selected_panel_time = time;
317 else if(nPrimary == K_DOWNARROW)
321 LABEL(downarrow_action);
322 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
324 if (scoreboard_ui_enabled == 2)
326 entity curr_team = NULL;
327 bool scoreboard_selected_team_found = false;
328 if (!scoreboard_selected_team)
329 scoreboard_selected_team_found = true;
331 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
333 if(tm.team == NUM_SPECTATOR)
336 if (scoreboard_selected_team_found)
338 if (scoreboard_selected_team == tm)
339 scoreboard_selected_team_found = true;
342 if (curr_team == scoreboard_selected_team) // loop reached the last team
344 scoreboard_selected_team = curr_team;
349 entity curr_pl = NULL;
350 bool scoreboard_selected_player_found = false;
351 if (!scoreboard_selected_player)
352 scoreboard_selected_player_found = true;
354 for(tm = teams.sort_next; tm; tm = tm.sort_next)
356 if(tm.team != NUM_SPECTATOR)
357 for(pl = players.sort_next; pl; pl = pl.sort_next)
359 if(pl.team != tm.team)
362 if (scoreboard_selected_player_found)
364 if (scoreboard_selected_player == pl)
365 scoreboard_selected_player_found = true;
369 if (curr_pl == scoreboard_selected_player) // loop reached the last player
371 scoreboard_selected_player = curr_pl;
375 else if(nPrimary == K_UPARROW)
379 LABEL(uparrow_action);
380 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
382 if (scoreboard_ui_enabled == 2)
384 entity prev_team = NULL;
385 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
387 if(tm.team == NUM_SPECTATOR)
389 if (tm == scoreboard_selected_team)
394 scoreboard_selected_team = prev_team;
398 entity prev_pl = NULL;
400 for(tm = teams.sort_next; tm; tm = tm.sort_next)
402 if(tm.team != NUM_SPECTATOR)
403 for(pl = players.sort_next; pl; pl = pl.sort_next)
405 if(pl.team != tm.team)
407 if (pl == scoreboard_selected_player)
413 scoreboard_selected_player = prev_pl;
417 else if(nPrimary == K_RIGHTARROW)
421 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
422 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
424 else if(nPrimary == K_LEFTARROW)
428 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
429 rankings_start_column = max(rankings_start_column - 1, 0);
431 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
435 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
437 if (scoreboard_ui_enabled == 2)
440 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
443 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
444 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
445 HUD_Scoreboard_UI_Disable();
447 else if (scoreboard_selected_player)
448 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
451 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
455 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
457 switch (scoreboard_selected_columns_layout)
460 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
462 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
463 scoreboard_selected_columns_layout = 1;
468 localcmd(sprintf("scoreboard_columns_set default\n"));
469 scoreboard_selected_columns_layout = 2;
472 localcmd(sprintf("scoreboard_columns_set all\n"));
473 scoreboard_selected_columns_layout = 0;
478 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
482 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
483 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
485 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
489 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
491 if (scoreboard_selected_player)
493 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
494 HUD_Scoreboard_UI_Disable();
498 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
502 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
504 if (scoreboard_selected_player)
505 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
508 else if(hit_con_bind || nPrimary == K_PAUSE)
514 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
515 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
517 void Scoreboard_InitScores()
521 ps_primary = ps_secondary = NULL;
522 ts_primary = ts_secondary = -1;
523 FOREACH(Scores, true, {
524 if(scores_flags(it) & SFL_NOT_SORTABLE)
526 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
527 if(f == SFL_SORT_PRIO_PRIMARY)
529 if(f == SFL_SORT_PRIO_SECONDARY)
532 if(ps_secondary == NULL)
533 ps_secondary = ps_primary;
535 for(i = 0; i < MAX_TEAMSCORE; ++i)
537 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
538 if(f == SFL_SORT_PRIO_PRIMARY)
540 if(f == SFL_SORT_PRIO_SECONDARY)
543 if(ts_secondary == -1)
544 ts_secondary = ts_primary;
546 Cmd_Scoreboard_SetFields(0);
550 void Scoreboard_UpdatePlayerTeams()
552 static float update_time;
553 if (time <= update_time)
560 for(pl = players.sort_next; pl; pl = pl.sort_next)
562 numplayers += pl.team != NUM_SPECTATOR;
564 int Team = entcs_GetScoreTeam(pl.sv_entnum);
565 if(SetTeam(pl, Team))
568 Scoreboard_UpdatePlayerPos(pl);
572 pl = players.sort_next;
577 print(strcat("PNUM: ", ftos(num), "\n"));
582 int Scoreboard_CompareScore(int vl, int vr, int f)
584 TC(int, vl); TC(int, vr); TC(int, f);
585 if(f & SFL_ZERO_IS_WORST)
587 if(vl == 0 && vr != 0)
589 if(vl != 0 && vr == 0)
593 return IS_INCREASING(f);
595 return IS_DECREASING(f);
599 float Scoreboard_ComparePlayerScores(entity left, entity right)
601 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
602 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
609 if(vl == NUM_SPECTATOR)
611 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
613 if(!left.gotscores && right.gotscores)
618 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
619 if (res >= 0) return res;
621 if (ps_secondary && ps_secondary != ps_primary)
623 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
624 if (res >= 0) return res;
627 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
628 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
629 if (res >= 0) return res;
632 if (left.sv_entnum < right.sv_entnum)
638 void Scoreboard_UpdatePlayerPos(entity player)
641 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
643 SORT_SWAP(player, ent);
645 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
647 SORT_SWAP(ent, player);
651 float Scoreboard_CompareTeamScores(entity left, entity right)
653 if(left.team == NUM_SPECTATOR)
655 if(right.team == NUM_SPECTATOR)
660 for(int i = -2; i < MAX_TEAMSCORE; ++i)
664 if (fld_idx == -1) fld_idx = ts_primary;
665 else if (ts_secondary == ts_primary) continue;
666 else fld_idx = ts_secondary;
671 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
674 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
675 if (r >= 0) return r;
678 if (left.team < right.team)
684 void Scoreboard_UpdateTeamPos(entity Team)
687 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
689 SORT_SWAP(Team, ent);
691 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
693 SORT_SWAP(ent, Team);
697 void Cmd_Scoreboard_Help()
699 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
700 LOG_HELP(_("Usage:"));
701 LOG_HELP("^2scoreboard_columns_set ^3default");
702 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
703 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
704 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
705 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
706 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
707 LOG_HELP(_("The following field names are recognized (case insensitive):"));
713 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
714 "of game types, then a slash, to make the field show up only in these\n"
715 "or in all but these game types. You can also specify 'all' as a\n"
716 "field to show all fields available for the current game mode."));
719 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
720 "include/exclude ALL teams/noteams game modes."));
723 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
724 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
725 "right of the vertical bar aligned to the right."));
726 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
727 "other gamemodes except DM."));
730 // NOTE: adding a gametype with ? to not warn for an optional field
731 // make sure it's excluded in a previous exclusive rule, if any
732 // otherwise the previous exclusive rule warns anyway
733 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
734 #define SCOREBOARD_DEFAULT_COLUMNS \
735 "ping pl fps name |" \
736 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
737 " -teams,lms/deaths +ft,tdm/deaths" \
739 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
740 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
741 " +tdm,ft,dom,ons,as/teamkills"\
742 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
743 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
744 " +lms/lives +lms/rank" \
745 " +kh/kckills +kh/losses +kh/caps" \
746 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
747 " +as/objectives +nb/faults +nb/goals" \
748 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
749 " +dom/ticks +dom/takes" \
750 " -lms,rc,cts,inv,nb/score"
752 void Cmd_Scoreboard_SetFields(int argc)
757 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
761 return; // do nothing, we don't know gametype and scores yet
763 // sbt_fields uses strunzone on the titles!
764 if(!sbt_field_title[0])
765 for(i = 0; i < MAX_SBT_FIELDS; ++i)
766 sbt_field_title[i] = strzone("(null)");
768 // TODO: re enable with gametype dependant cvars?
769 if(argc < 3) // no arguments provided
770 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
773 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
777 if(argv(2) == "default" || argv(2) == "expand_default")
779 if(argv(2) == "expand_default")
780 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
781 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
783 else if(argv(2) == "all" || argv(2) == "ALL")
785 string s = "ping pl name |"; // scores without label (not really scores)
788 // scores without label
789 s = strcat(s, " ", "sum");
790 s = strcat(s, " ", "kdratio");
791 s = strcat(s, " ", "frags");
793 FOREACH(Scores, true, {
795 if(it != ps_secondary)
796 if(scores_label(it) != "")
797 s = strcat(s, " ", scores_label(it));
799 if(ps_secondary != ps_primary)
800 s = strcat(s, " ", scores_label(ps_secondary));
801 s = strcat(s, " ", scores_label(ps_primary));
802 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
809 hud_fontsize = HUD_GetFontsize("hud_fontsize");
811 for(i = 1; i < argc - 1; ++i)
814 bool nocomplain = false;
815 if(substring(str, 0, 1) == "?")
818 str = substring(str, 1, strlen(str) - 1);
821 slash = strstrofs(str, "/", 0);
824 pattern = substring(str, 0, slash);
825 str = substring(str, slash + 1, strlen(str) - (slash + 1));
827 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
831 str = strtolower(str);
832 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
833 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
838 // fields without a label (not networked via the score system)
839 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
840 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
841 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
842 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
843 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
844 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
845 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
846 default: // fields with a label
848 // map alternative labels
849 if (str == "damage") str = "dmg";
850 if (str == "damagetaken") str = "dmgtaken";
852 FOREACH(Scores, true, {
853 if (str == strtolower(scores_label(it))) {
855 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
859 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
860 if(!nocomplain && str != "fps") // server can disable the fps field
861 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
863 strfree(sbt_field_title[sbt_num_fields]);
864 sbt_field_size[sbt_num_fields] = 0;
868 sbt_field[sbt_num_fields] = j;
871 if(j == ps_secondary)
872 have_secondary = true;
877 if(sbt_num_fields >= MAX_SBT_FIELDS)
881 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
883 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
884 have_secondary = true;
885 if(ps_primary == ps_secondary)
886 have_secondary = true;
887 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
889 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
893 strfree(sbt_field_title[sbt_num_fields]);
894 for(i = sbt_num_fields; i > 0; --i)
896 sbt_field_title[i] = sbt_field_title[i-1];
897 sbt_field_size[i] = sbt_field_size[i-1];
898 sbt_field[i] = sbt_field[i-1];
900 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
901 sbt_field[0] = SP_NAME;
903 LOG_INFO("fixed missing field 'name'");
907 strfree(sbt_field_title[sbt_num_fields]);
908 for(i = sbt_num_fields; i > 1; --i)
910 sbt_field_title[i] = sbt_field_title[i-1];
911 sbt_field_size[i] = sbt_field_size[i-1];
912 sbt_field[i] = sbt_field[i-1];
914 sbt_field_title[1] = strzone("|");
915 sbt_field[1] = SP_SEPARATOR;
916 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
918 LOG_INFO("fixed missing field '|'");
921 else if(!have_separator)
923 strcpy(sbt_field_title[sbt_num_fields], "|");
924 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
925 sbt_field[sbt_num_fields] = SP_SEPARATOR;
927 LOG_INFO("fixed missing field '|'");
931 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
932 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
933 sbt_field[sbt_num_fields] = ps_secondary;
935 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
939 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
940 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
941 sbt_field[sbt_num_fields] = ps_primary;
943 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
947 sbt_field[sbt_num_fields] = SP_END;
950 string Scoreboard_AddPlayerId(string pl_name, entity pl)
952 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
953 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
954 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
958 vector sbt_field_rgb;
959 string sbt_field_icon0;
960 string sbt_field_icon1;
961 string sbt_field_icon2;
962 vector sbt_field_icon0_rgb;
963 vector sbt_field_icon1_rgb;
964 vector sbt_field_icon2_rgb;
965 string Scoreboard_GetName(entity pl)
967 if(ready_waiting && pl.ready)
969 sbt_field_icon0 = "gfx/scoreboard/player_ready";
973 int f = entcs_GetClientColors(pl.sv_entnum);
975 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
976 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
977 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
978 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
979 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
982 return entcs_GetName(pl.sv_entnum);
985 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
987 float tmp, num, denom;
990 sbt_field_rgb = '1 1 1';
991 sbt_field_icon0 = "";
992 sbt_field_icon1 = "";
993 sbt_field_icon2 = "";
994 sbt_field_icon0_rgb = '1 1 1';
995 sbt_field_icon1_rgb = '1 1 1';
996 sbt_field_icon2_rgb = '1 1 1';
997 int rounds_played = 0;
999 rounds_played = pl.(scores(SP_ROUNDS_PL));
1004 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1005 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1009 tmp = max(0, min(220, f-80)) / 220;
1010 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1016 f = pl.ping_packetloss;
1017 tmp = pl.ping_movementloss;
1018 if(f == 0 && tmp == 0)
1020 str = ftos(ceil(f * 100));
1022 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1023 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1024 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1028 str = Scoreboard_GetName(pl);
1029 if (autocvar_hud_panel_scoreboard_playerid)
1030 str = Scoreboard_AddPlayerId(str, pl);
1034 f = pl.(scores(SP_KILLS));
1035 f -= pl.(scores(SP_SUICIDES));
1037 return sprintf("%.1f", f / rounds_played);
1041 num = pl.(scores(SP_KILLS));
1042 denom = pl.(scores(SP_DEATHS));
1045 sbt_field_rgb = '0 1 0';
1047 str = sprintf("%.1f", num / rounds_played);
1049 str = sprintf("%d", num);
1050 } else if(num <= 0) {
1051 sbt_field_rgb = '1 0 0';
1053 str = sprintf("%.2f", num / (denom * rounds_played));
1055 str = sprintf("%.1f", num / denom);
1059 str = sprintf("%.2f", num / (denom * rounds_played));
1061 str = sprintf("%.1f", num / denom);
1066 f = pl.(scores(SP_KILLS));
1067 f -= pl.(scores(SP_DEATHS));
1070 sbt_field_rgb = '0 1 0';
1072 sbt_field_rgb = '1 1 1';
1074 sbt_field_rgb = '1 0 0';
1077 return sprintf("%.1f", f / rounds_played);
1082 float elo = pl.(scores(SP_ELO));
1084 case -1: return "...";
1085 case -2: return _("N/A");
1086 default: return ftos(elo);
1092 float fps = pl.(scores(SP_FPS));
1095 sbt_field_rgb = '1 1 1';
1096 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1098 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1099 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1104 return ftos(pl.(scores(field)));
1106 case SP_DMG: case SP_DMGTAKEN:
1108 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1109 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1111 default: case SP_SCORE:
1112 tmp = pl.(scores(field));
1113 f = scores_flags(field);
1114 if(field == ps_primary)
1115 sbt_field_rgb = '1 1 0';
1116 else if(field == ps_secondary)
1117 sbt_field_rgb = '0 1 1';
1119 sbt_field_rgb = '1 1 1';
1120 return ScoreString(f, tmp, rounds_played);
1125 float sbt_fixcolumnwidth_len;
1126 float sbt_fixcolumnwidth_iconlen;
1127 float sbt_fixcolumnwidth_marginlen;
1129 string Scoreboard_FixColumnWidth(int i, string str)
1135 sbt_fixcolumnwidth_iconlen = 0;
1137 if(sbt_field_icon0 != "")
1139 sz = draw_getimagesize(sbt_field_icon0);
1141 if(sbt_fixcolumnwidth_iconlen < f)
1142 sbt_fixcolumnwidth_iconlen = f;
1145 if(sbt_field_icon1 != "")
1147 sz = draw_getimagesize(sbt_field_icon1);
1149 if(sbt_fixcolumnwidth_iconlen < f)
1150 sbt_fixcolumnwidth_iconlen = f;
1153 if(sbt_field_icon2 != "")
1155 sz = draw_getimagesize(sbt_field_icon2);
1157 if(sbt_fixcolumnwidth_iconlen < f)
1158 sbt_fixcolumnwidth_iconlen = f;
1161 if(sbt_fixcolumnwidth_iconlen != 0)
1163 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1164 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1167 sbt_fixcolumnwidth_marginlen = 0;
1169 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1172 float remaining_space = 0;
1173 for(j = 0; j < sbt_num_fields; ++j)
1175 if (sbt_field[i] != SP_SEPARATOR)
1176 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1177 sbt_field_size[i] = panel_size.x - remaining_space;
1179 if (sbt_fixcolumnwidth_iconlen != 0)
1180 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1181 float namesize = panel_size.x - remaining_space;
1182 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1183 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1185 max_namesize = vid_conwidth - remaining_space;
1188 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1190 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1191 if(sbt_field_size[i] < f)
1192 sbt_field_size[i] = f;
1197 void Scoreboard_initFieldSizes()
1199 for(int i = 0; i < sbt_num_fields; ++i)
1201 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1202 Scoreboard_FixColumnWidth(i, "");
1206 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1209 vector column_dim = eY * panel_size.y;
1211 column_dim.y -= 1.25 * hud_fontsize.y;
1212 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1213 pos.x += hud_fontsize.x * 0.5;
1214 for(i = 0; i < sbt_num_fields; ++i)
1216 if(sbt_field[i] == SP_SEPARATOR)
1218 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1221 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1222 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1223 pos.x += column_dim.x;
1225 if(sbt_field[i] == SP_SEPARATOR)
1227 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1228 for(i = sbt_num_fields - 1; i > 0; --i)
1230 if(sbt_field[i] == SP_SEPARATOR)
1233 pos.x -= sbt_field_size[i];
1238 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1239 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1242 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1243 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 pos.x -= hud_fontsize.x;
1248 pos.x = panel_pos.x;
1249 pos.y += 1.25 * hud_fontsize.y;
1253 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1255 TC(bool, is_self); TC(int, pl_number);
1257 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1259 vector h_pos = item_pos;
1260 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1261 // alternated rows highlighting
1262 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1264 if (pl == scoreboard_selected_player)
1265 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1268 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1269 else if((sbt_highlight) && (!(pl_number % 2)))
1270 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1272 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1274 vector pos = item_pos;
1275 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1277 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1279 pos.x += hud_fontsize.x * 0.5;
1280 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1281 vector tmp = '0 0 0';
1283 PlayerScoreField field;
1284 for(i = 0; i < sbt_num_fields; ++i)
1286 field = sbt_field[i];
1287 if(field == SP_SEPARATOR)
1290 if(is_spec && field != SP_NAME && field != SP_PING) {
1291 pos.x += sbt_field_size[i] + hud_fontsize.x;
1294 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1295 str = Scoreboard_FixColumnWidth(i, str);
1297 pos.x += sbt_field_size[i] + hud_fontsize.x;
1299 if(field == SP_NAME) {
1300 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1301 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1303 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1304 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1307 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1308 if(sbt_field_icon0 != "")
1309 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1310 if(sbt_field_icon1 != "")
1311 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1312 if(sbt_field_icon2 != "")
1313 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1316 if(sbt_field[i] == SP_SEPARATOR)
1318 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1319 for(i = sbt_num_fields-1; i > 0; --i)
1321 field = sbt_field[i];
1322 if(field == SP_SEPARATOR)
1325 if(is_spec && field != SP_NAME && field != SP_PING) {
1326 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1330 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1331 str = Scoreboard_FixColumnWidth(i, str);
1333 if(field == SP_NAME) {
1334 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1335 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1337 tmp.x = sbt_fixcolumnwidth_len;
1338 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1341 tmp.x = sbt_field_size[i];
1342 if(sbt_field_icon0 != "")
1343 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1344 if(sbt_field_icon1 != "")
1345 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1346 if(sbt_field_icon2 != "")
1347 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1348 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1353 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1356 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1359 vector h_pos = item_pos;
1360 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1362 bool complete = (this_team == NUM_SPECTATOR);
1365 if((sbt_highlight) && (!(pl_number % 2)))
1366 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1368 vector pos = item_pos;
1369 pos.x += hud_fontsize.x * 0.5;
1370 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1372 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1374 width_limit -= stringwidth("...", false, hud_fontsize);
1375 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1376 static float max_name_width = 0;
1378 float fieldsize = 0;
1379 float min_fieldsize = 0;
1380 float fieldpadding = hud_fontsize.x * 0.25;
1381 if(this_team == NUM_SPECTATOR)
1383 if(autocvar_hud_panel_scoreboard_spectators_showping)
1384 min_fieldsize = stringwidth("999", false, hud_fontsize);
1386 else if(autocvar_hud_panel_scoreboard_others_showscore)
1387 min_fieldsize = stringwidth("99", false, hud_fontsize);
1388 for(i = 0; pl; pl = pl.sort_next)
1390 if(pl.team != this_team)
1392 if(pl == ignored_pl)
1396 if(this_team == NUM_SPECTATOR)
1398 if(autocvar_hud_panel_scoreboard_spectators_showping)
1399 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1401 else if(autocvar_hud_panel_scoreboard_others_showscore)
1402 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1404 string str = entcs_GetName(pl.sv_entnum);
1405 if (autocvar_hud_panel_scoreboard_playerid)
1406 str = Scoreboard_AddPlayerId(str, pl);
1407 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1408 float column_width = stringwidth(str, true, hud_fontsize);
1409 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1411 if(column_width > max_name_width)
1412 max_name_width = column_width;
1413 column_width = max_name_width;
1417 fieldsize = stringwidth(field, false, hud_fontsize);
1418 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1421 if(pos.x + column_width > width_limit)
1426 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1431 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1432 pos.y += hud_fontsize.y * 1.25;
1436 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1438 if (pl == scoreboard_selected_player)
1440 h_size.x = column_width + hud_fontsize.x * 0.25;
1441 h_size.y = hud_fontsize.y;
1442 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1446 vector name_pos = pos;
1447 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1448 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1449 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1453 h_size.y = hud_fontsize.y;
1454 vector field_pos = pos;
1455 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1456 field_pos.x += column_width - h_size.x;
1458 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1459 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1460 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1464 h_size.x = column_width + hud_fontsize.x * 0.25;
1465 h_size.y = hud_fontsize.y;
1466 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1468 pos.x += column_width;
1469 pos.x += hud_fontsize.x;
1471 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1474 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1476 int max_players = 999;
1477 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1479 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1482 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1483 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1484 height /= team_count;
1487 height -= panel_bg_padding * 2; // - padding
1488 max_players = floor(height / (hud_fontsize.y * 1.25));
1489 if(max_players <= 1)
1491 if(max_players == tm.team_size)
1496 entity me = playerslots[current_player];
1498 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1499 panel_size.y += panel_bg_padding * 2;
1501 vector scoreboard_selected_hl_pos = pos;
1502 vector scoreboard_selected_hl_size = '0 0 0';
1503 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1504 scoreboard_selected_hl_size.y = panel_size.y;
1508 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1509 if(panel.current_panel_bg != "0")
1510 end_pos.y += panel_bg_border * 2;
1512 if(panel_bg_padding)
1514 panel_pos += '1 1 0' * panel_bg_padding;
1515 panel_size -= '2 2 0' * panel_bg_padding;
1519 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1523 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1525 pos.y += 1.25 * hud_fontsize.y;
1528 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1530 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1533 // print header row and highlight columns
1534 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1536 // fill the table and draw the rows
1537 bool is_self = false;
1538 bool self_shown = false;
1540 for(pl = players.sort_next; pl; pl = pl.sort_next)
1542 if(pl.team != tm.team)
1544 if(i == max_players - 2 && pl != me)
1546 if(!self_shown && me.team == tm.team)
1548 Scoreboard_DrawItem(pos, rgb, me, true, i);
1550 pos.y += 1.25 * hud_fontsize.y;
1554 if(i >= max_players - 1)
1556 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1559 is_self = (pl.sv_entnum == current_player);
1560 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1563 pos.y += 1.25 * hud_fontsize.y;
1567 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1569 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1571 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1572 _alpha *= panel_fg_alpha;
1574 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1578 panel_size.x += panel_bg_padding * 2; // restore initial width
1582 bool Scoreboard_WouldDraw()
1584 if (scoreboard_ui_enabled)
1586 if (scoreboard_ui_disabling)
1588 if (scoreboard_fade_alpha == 0)
1589 HUD_Scoreboard_UI_Disable_Instantly();
1592 if (intermission && scoreboard_ui_enabled == 2)
1594 HUD_Scoreboard_UI_Disable_Instantly();
1599 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1601 else if (QuickMenu_IsOpened())
1603 else if (HUD_Radar_Clickable())
1605 else if (sb_showscores) // set by +showscores engine command
1607 else if (intermission == 1)
1609 else if (intermission == 2)
1611 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1612 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1616 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1621 float average_accuracy;
1622 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1624 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1626 WepSet weapons_stat = WepSet_GetFromStat();
1627 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1628 int disownedcnt = 0;
1630 FOREACH(Weapons, it != WEP_Null, {
1631 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1633 WepSet set = it.m_wepset;
1634 if(it.spawnflags & WEP_TYPE_OTHER)
1639 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1641 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1648 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1649 if (weapon_cnt <= 0) return pos;
1652 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1654 int columns = ceil(weapon_cnt / rows);
1656 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1657 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1658 float height = weapon_height + hud_fontsize.y;
1660 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);
1661 pos.y += 1.25 * hud_fontsize.y;
1662 if(panel.current_panel_bg != "0")
1663 pos.y += panel_bg_border;
1666 panel_size.y = height * rows;
1667 panel_size.y += panel_bg_padding * 2;
1669 float panel_bg_alpha_save = panel_bg_alpha;
1670 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1672 panel_bg_alpha = panel_bg_alpha_save;
1674 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1675 if(panel.current_panel_bg != "0")
1676 end_pos.y += panel_bg_border * 2;
1678 if(panel_bg_padding)
1680 panel_pos += '1 1 0' * panel_bg_padding;
1681 panel_size -= '2 2 0' * panel_bg_padding;
1685 vector tmp = panel_size;
1687 float weapon_width = tmp.x / columns / rows;
1690 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1694 // column highlighting
1695 for (int i = 0; i < columns; ++i)
1697 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);
1700 for (int i = 0; i < rows; ++i)
1701 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1704 average_accuracy = 0;
1705 int weapons_with_stats = 0;
1707 pos.x += weapon_width / 2;
1709 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1712 Accuracy_LoadColors();
1714 float oldposx = pos.x;
1718 FOREACH(Weapons, it != WEP_Null, {
1719 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1721 WepSet set = it.m_wepset;
1722 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1724 if (it.spawnflags & WEP_TYPE_OTHER)
1728 if (weapon_stats >= 0)
1729 weapon_alpha = sbt_fg_alpha;
1731 weapon_alpha = 0.2 * sbt_fg_alpha;
1734 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1736 if (weapon_stats >= 0) {
1737 weapons_with_stats += 1;
1738 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1740 string s = sprintf("%d%%", weapon_stats * 100);
1741 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1743 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1744 rgb = Accuracy_GetColor(weapon_stats);
1746 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1748 tmpos.x += weapon_width * rows;
1749 pos.x += weapon_width * rows;
1750 if (rows == 2 && column == columns - 1) {
1758 if (weapons_with_stats)
1759 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1761 panel_size.x += panel_bg_padding * 2; // restore initial width
1766 bool is_item_filtered(entity it)
1768 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1770 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1773 if (it.instanceOfArmor || it.instanceOfHealth)
1775 int ha_mask = floor(mask) % 10;
1778 default: return false;
1779 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1780 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1781 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1782 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1785 if (it.instanceOfAmmo)
1787 int ammo_mask = floor(mask / 10) % 10;
1788 return (ammo_mask == 1);
1793 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1795 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1797 int disowned_cnt = 0;
1798 int uninteresting_cnt = 0;
1799 IL_EACH(default_order_items, true, {
1800 int q = g_inventory.inv_items[it.m_id];
1801 //q = 1; // debug: display all items
1802 if (is_item_filtered(it))
1803 ++uninteresting_cnt;
1807 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1808 int n = items_cnt - disowned_cnt;
1809 if (n <= 0) return pos;
1811 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1812 int columns = max(6, ceil(n / rows));
1814 float item_height = hud_fontsize.y * 2.3;
1815 float height = item_height + hud_fontsize.y;
1817 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1818 pos.y += 1.25 * hud_fontsize.y;
1819 if(panel.current_panel_bg != "0")
1820 pos.y += panel_bg_border;
1823 panel_size.y = height * rows;
1824 panel_size.y += panel_bg_padding * 2;
1826 float panel_bg_alpha_save = panel_bg_alpha;
1827 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1829 panel_bg_alpha = panel_bg_alpha_save;
1831 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1832 if(panel.current_panel_bg != "0")
1833 end_pos.y += panel_bg_border * 2;
1835 if(panel_bg_padding)
1837 panel_pos += '1 1 0' * panel_bg_padding;
1838 panel_size -= '2 2 0' * panel_bg_padding;
1842 vector tmp = panel_size;
1844 float item_width = tmp.x / columns / rows;
1847 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1851 // column highlighting
1852 for (int i = 0; i < columns; ++i)
1854 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);
1857 for (int i = 0; i < rows; ++i)
1858 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1862 pos.x += item_width / 2;
1864 float oldposx = pos.x;
1868 IL_EACH(default_order_items, !is_item_filtered(it), {
1869 int n = g_inventory.inv_items[it.m_id];
1870 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1871 if (n <= 0) continue;
1872 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);
1874 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1875 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1876 tmpos.x += item_width * rows;
1877 pos.x += item_width * rows;
1878 if (rows == 2 && column == columns - 1) {
1886 panel_size.x += panel_bg_padding * 2; // restore initial width
1891 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1893 pos.x += hud_fontsize.x * 0.25;
1894 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1895 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1896 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1898 pos.y += hud_fontsize.y;
1903 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1904 float stat_secrets_found, stat_secrets_total;
1905 float stat_monsters_killed, stat_monsters_total;
1909 // get monster stats
1910 stat_monsters_killed = STAT(MONSTERS_KILLED);
1911 stat_monsters_total = STAT(MONSTERS_TOTAL);
1913 // get secrets stats
1914 stat_secrets_found = STAT(SECRETS_FOUND);
1915 stat_secrets_total = STAT(SECRETS_TOTAL);
1917 // get number of rows
1918 if(stat_secrets_total)
1920 if(stat_monsters_total)
1923 // if no rows, return
1927 // draw table header
1928 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1929 pos.y += 1.25 * hud_fontsize.y;
1930 if(panel.current_panel_bg != "0")
1931 pos.y += panel_bg_border;
1934 panel_size.y = hud_fontsize.y * rows;
1935 panel_size.y += panel_bg_padding * 2;
1938 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1939 if(panel.current_panel_bg != "0")
1940 end_pos.y += panel_bg_border * 2;
1942 if(panel_bg_padding)
1944 panel_pos += '1 1 0' * panel_bg_padding;
1945 panel_size -= '2 2 0' * panel_bg_padding;
1949 vector tmp = panel_size;
1952 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1955 if(stat_monsters_total)
1957 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1958 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1962 if(stat_secrets_total)
1964 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1965 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1968 panel_size.x += panel_bg_padding * 2; // restore initial width
1972 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1975 RANKINGS_RECEIVED_CNT = 0;
1976 for (i=RANKINGS_CNT-1; i>=0; --i)
1978 ++RANKINGS_RECEIVED_CNT;
1980 if (RANKINGS_RECEIVED_CNT == 0)
1983 vector hl_rgb = rgb + '0.5 0.5 0.5';
1985 vector scoreboard_selected_hl_pos = pos;
1987 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1988 pos.y += 1.25 * hud_fontsize.y;
1989 if(panel.current_panel_bg != "0")
1990 pos.y += panel_bg_border;
1992 vector scoreboard_selected_hl_size = '0 0 0';
1993 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1994 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1999 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2001 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2006 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2008 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2012 float ranksize = 3 * hud_fontsize.x;
2013 float timesize = 5 * hud_fontsize.x;
2014 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2015 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2016 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2019 rankings_cnt = RANKINGS_RECEIVED_CNT;
2020 rankings_rows = ceil(rankings_cnt / rankings_columns);
2023 // expand name column to fill the entire row
2024 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2025 namesize += available_space;
2026 columnsize.x += available_space;
2028 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2029 panel_size.y += panel_bg_padding * 2;
2030 scoreboard_selected_hl_size.y += panel_size.y;
2034 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2035 if(panel.current_panel_bg != "0")
2036 end_pos.y += panel_bg_border * 2;
2038 if(panel_bg_padding)
2040 panel_pos += '1 1 0' * panel_bg_padding;
2041 panel_size -= '2 2 0' * panel_bg_padding;
2047 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2049 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2051 int column = 0, j = 0;
2052 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2053 int start_item = rankings_start_column * rankings_rows;
2054 for(i = start_item; i < start_item + rankings_cnt; ++i)
2056 int t = grecordtime[i];
2060 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2061 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2062 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2063 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2065 str = count_ordinal(i+1);
2066 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2067 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2068 str = ColorTranslateRGB(grecordholder[i]);
2070 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2071 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2073 pos.y += 1.25 * hud_fontsize.y;
2075 if(j >= rankings_rows)
2079 pos.x += panel_size.x / rankings_columns;
2080 pos.y = panel_pos.y;
2083 strfree(zoned_name_self);
2085 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2087 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2088 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2091 panel_size.x += panel_bg_padding * 2; // restore initial width
2095 bool have_weapon_stats;
2096 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2098 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2100 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2103 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2104 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2110 if (!have_weapon_stats)
2112 FOREACH(Weapons, it != WEP_Null, {
2113 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2114 if (weapon_stats >= 0)
2116 have_weapon_stats = true;
2120 if (!have_weapon_stats)
2127 bool have_item_stats;
2128 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2130 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2132 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2135 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2136 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2142 if (!have_item_stats)
2144 IL_EACH(default_order_items, true, {
2145 if (!is_item_filtered(it))
2147 int q = g_inventory.inv_items[it.m_id];
2148 //q = 1; // debug: display all items
2151 have_item_stats = true;
2156 if (!have_item_stats)
2163 vector Scoreboard_Spectators_Draw(vector pos) {
2168 for(pl = players.sort_next; pl; pl = pl.sort_next)
2170 if(pl.team == NUM_SPECTATOR)
2172 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2173 if(tm.team == NUM_SPECTATOR)
2175 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2176 draw_beginBoldFont();
2177 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2179 pos.y += 1.25 * hud_fontsize.y;
2181 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2182 pos.y += 1.25 * hud_fontsize.y;
2187 if (str != "") // if there's at least one spectator
2188 pos.y += 0.5 * hud_fontsize.y;
2193 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2195 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2196 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2197 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2198 (s_label == "score") ? CTX(_("SCO^points")) :
2199 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2202 void Scoreboard_Draw()
2204 if(!autocvar__hud_configure)
2206 if(!hud_draw_maximized) return;
2208 // frametime checks allow to toggle the scoreboard even when the game is paused
2209 if(scoreboard_active) {
2210 if (scoreboard_fade_alpha == 0)
2211 scoreboard_time = time;
2212 if(hud_configure_menu_open == 1)
2213 scoreboard_fade_alpha = 1;
2214 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2215 if (scoreboard_fadeinspeed && frametime)
2216 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2218 scoreboard_fade_alpha = 1;
2219 if(hud_fontsize_str != autocvar_hud_fontsize)
2221 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2222 Scoreboard_initFieldSizes();
2223 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2227 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2228 if (scoreboard_fadeoutspeed && frametime)
2229 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2231 scoreboard_fade_alpha = 0;
2234 if (!scoreboard_fade_alpha)
2236 scoreboard_acc_fade_alpha = 0;
2237 scoreboard_itemstats_fade_alpha = 0;
2242 scoreboard_fade_alpha = 0;
2244 if (autocvar_hud_panel_scoreboard_dynamichud)
2247 HUD_Scale_Disable();
2249 if(scoreboard_fade_alpha <= 0)
2251 panel_fade_alpha *= scoreboard_fade_alpha;
2252 HUD_Panel_LoadCvars();
2254 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2255 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2256 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2257 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2258 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2259 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2260 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2262 // don't overlap with con_notify
2263 if(!autocvar__hud_configure)
2264 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2266 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2267 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2268 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2269 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2270 panel_pos.x = scoreboard_left;
2271 panel_size.x = fixed_scoreboard_width;
2273 Scoreboard_UpdatePlayerTeams();
2275 scoreboard_top = panel_pos.y;
2276 vector pos = panel_pos;
2281 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2283 // Begin of Game Info Section
2284 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2285 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2287 // Game Info: Game Type
2288 if (scoreboard_ui_enabled == 2)
2289 str = _("Team Selection");
2291 str = MapInfo_Type_ToText(gametype);
2292 draw_beginBoldFont();
2293 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);
2296 pos.y += sb_gameinfo_type_fontsize.y;
2297 // Game Info: Game Detail
2298 if (scoreboard_ui_enabled == 2)
2300 if (scoreboard_selected_team)
2301 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2303 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2304 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);
2306 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2307 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2308 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);
2312 float tl = STAT(TIMELIMIT);
2313 float fl = STAT(FRAGLIMIT);
2314 float ll = STAT(LEADLIMIT);
2315 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2318 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2319 if(!gametype.m_hidelimits)
2324 str = strcat(str, "^7 / "); // delimiter
2325 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2329 if(tl > 0 || fl > 0)
2332 if (ll_and_fl && fl > 0)
2333 str = strcat(str, "^7 & ");
2335 str = strcat(str, "^7 / ");
2337 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2340 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
2341 // map name and player count
2345 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2346 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2347 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2349 // End of Game Info Section
2351 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2352 if(panel.current_panel_bg != "0")
2353 pos.y += panel_bg_border;
2355 // Draw the scoreboard
2356 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2359 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2363 vector panel_bg_color_save = panel_bg_color;
2364 vector team_score_baseoffset;
2365 vector team_size_baseoffset;
2366 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2368 // put team score to the left of scoreboard (and team size to the right)
2369 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2370 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2371 if(panel.current_panel_bg != "0")
2373 team_score_baseoffset.x -= panel_bg_border;
2374 team_size_baseoffset.x += panel_bg_border;
2379 // put team score to the right of scoreboard (and team size to the left)
2380 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2381 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2382 if(panel.current_panel_bg != "0")
2384 team_score_baseoffset.x += panel_bg_border;
2385 team_size_baseoffset.x -= panel_bg_border;
2389 int team_size_total = 0;
2390 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2392 // calculate team size total (sum of all team sizes)
2393 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2394 if(tm.team != NUM_SPECTATOR)
2395 team_size_total += tm.team_size;
2398 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2400 if(tm.team == NUM_SPECTATOR)
2405 draw_beginBoldFont();
2406 vector rgb = Team_ColorRGB(tm.team);
2407 str = ftos(tm.(teamscores(ts_primary)));
2408 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2410 // team score on the left (default)
2411 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2415 // team score on the right
2416 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2418 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2420 // team size (if set to show on the side)
2421 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2423 // calculate the starting position for the whole team size info string
2424 str = sprintf("%d/%d", tm.team_size, team_size_total);
2425 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2427 // team size on the left
2428 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2432 // team size on the right
2433 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2435 str = sprintf("%d", tm.team_size);
2436 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2437 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2438 str = sprintf("/%d", team_size_total);
2439 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2443 // secondary score, e.g. keyhunt
2444 if(ts_primary != ts_secondary)
2446 str = ftos(tm.(teamscores(ts_secondary)));
2447 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2450 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2455 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2458 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2461 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2462 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2463 else if(panel_bg_color_team > 0)
2464 panel_bg_color = rgb * panel_bg_color_team;
2466 panel_bg_color = rgb;
2467 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2469 panel_bg_color = panel_bg_color_save;
2473 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2474 if(tm.team != NUM_SPECTATOR)
2477 // display it anyway
2478 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2481 // draw scoreboard spectators before accuracy and item stats
2482 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2483 pos = Scoreboard_Spectators_Draw(pos);
2486 // draw accuracy and item stats
2487 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2488 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2489 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2490 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2492 // draw scoreboard spectators after accuracy and item stats and before rankings
2493 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2494 pos = Scoreboard_Spectators_Draw(pos);
2497 if(MUTATOR_CALLHOOK(ShowRankings)) {
2498 string ranktitle = M_ARGV(0, string);
2499 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2500 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2501 if(race_speedaward_alltimebest)
2504 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2508 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2509 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2510 str = strcat(str, " / ");
2512 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2513 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2514 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2515 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2517 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2522 // draw scoreboard spectators after rankings
2523 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2524 pos = Scoreboard_Spectators_Draw(pos);
2527 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2529 // draw scoreboard spectators after mapstats
2530 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2531 pos = Scoreboard_Spectators_Draw(pos);
2535 // print information about respawn status
2536 float respawn_time = STAT(RESPAWN_TIME);
2537 if(!intermission && respawn_time)
2539 if(respawn_time < 0)
2541 // a negative number means we are awaiting respawn, time value is still the same
2542 respawn_time *= -1; // remove mark now that we checked it
2544 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2545 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2547 str = sprintf(_("^1Respawning in ^3%s^1..."),
2548 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2549 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2551 count_seconds(ceil(respawn_time - time))
2555 else if(time < respawn_time)
2557 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2558 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2559 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2561 count_seconds(ceil(respawn_time - time))
2565 else if(time >= respawn_time)
2566 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2568 pos.y += 1.2 * hud_fontsize.y;
2569 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2572 pos.y += hud_fontsize.y;
2573 if (scoreboard_fade_alpha < 1)
2574 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2575 else if (pos.y != scoreboard_bottom)
2577 if (pos.y > scoreboard_bottom)
2578 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2580 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2585 if (scoreboard_fade_alpha == 1)
2587 if (scoreboard_bottom > 0.95 * vid_conheight)
2588 rankings_rows = max(1, rankings_rows - 1);
2589 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2590 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2592 rankings_cnt = rankings_rows * rankings_columns;