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 int autocvar_hud_panel_scoreboard_ping_best = 0;
986 int autocvar_hud_panel_scoreboard_ping_medium = 70;
987 int autocvar_hud_panel_scoreboard_ping_high = 100;
988 int autocvar_hud_panel_scoreboard_ping_worst = 150;
989 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
990 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
991 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
992 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
993 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
994 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
995 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
996 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
997 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
998 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
999 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1000 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1001 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1003 float tmp, num, denom;
1006 sbt_field_rgb = '1 1 1';
1007 sbt_field_icon0 = "";
1008 sbt_field_icon1 = "";
1009 sbt_field_icon2 = "";
1010 sbt_field_icon0_rgb = '1 1 1';
1011 sbt_field_icon1_rgb = '1 1 1';
1012 sbt_field_icon2_rgb = '1 1 1';
1013 int rounds_played = 0;
1015 rounds_played = pl.(scores(SP_ROUNDS_PL));
1020 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1021 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1026 sbt_field_rgb = COLOR_BEST;
1027 else if(f < PING_MED)
1028 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1029 else if(f < PING_HIGH)
1030 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1031 else if(f < PING_WORST)
1032 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1034 sbt_field_rgb = COLOR_WORST;
1040 f = pl.ping_packetloss;
1041 tmp = pl.ping_movementloss;
1042 if(f == 0 && tmp == 0)
1044 str = ftos(ceil(f * 100));
1046 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1047 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1048 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1052 str = Scoreboard_GetName(pl);
1053 if (autocvar_hud_panel_scoreboard_playerid)
1054 str = Scoreboard_AddPlayerId(str, pl);
1058 f = pl.(scores(SP_KILLS));
1059 f -= pl.(scores(SP_SUICIDES));
1061 return sprintf("%.1f", f / rounds_played);
1065 num = pl.(scores(SP_KILLS));
1066 denom = pl.(scores(SP_DEATHS));
1069 sbt_field_rgb = '0 1 0';
1071 str = sprintf("%.1f", num / rounds_played);
1073 str = sprintf("%d", num);
1074 } else if(num <= 0) {
1075 sbt_field_rgb = '1 0 0';
1077 str = sprintf("%.2f", num / (denom * rounds_played));
1079 str = sprintf("%.1f", num / denom);
1083 str = sprintf("%.2f", num / (denom * rounds_played));
1085 str = sprintf("%.1f", num / denom);
1090 f = pl.(scores(SP_KILLS));
1091 f -= pl.(scores(SP_DEATHS));
1094 sbt_field_rgb = '0 1 0';
1096 sbt_field_rgb = '1 1 1';
1098 sbt_field_rgb = '1 0 0';
1101 return sprintf("%.1f", f / rounds_played);
1106 float elo = pl.(scores(SP_ELO));
1108 case -1: return "...";
1109 case -2: return _("N/A");
1110 default: return ftos(elo);
1116 float fps = pl.(scores(SP_FPS));
1119 sbt_field_rgb = '1 1 1';
1120 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1122 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1123 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1128 return ftos(pl.(scores(field)));
1130 case SP_DMG: case SP_DMGTAKEN:
1132 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1133 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1135 default: case SP_SCORE:
1136 tmp = pl.(scores(field));
1137 f = scores_flags(field);
1138 if(field == ps_primary)
1139 sbt_field_rgb = '1 1 0';
1140 else if(field == ps_secondary)
1141 sbt_field_rgb = '0 1 1';
1143 sbt_field_rgb = '1 1 1';
1144 return ScoreString(f, tmp, rounds_played);
1149 float sbt_fixcolumnwidth_len;
1150 float sbt_fixcolumnwidth_iconlen;
1151 float sbt_fixcolumnwidth_marginlen;
1153 string Scoreboard_FixColumnWidth(int i, string str)
1159 sbt_fixcolumnwidth_iconlen = 0;
1161 if(sbt_field_icon0 != "")
1163 sz = draw_getimagesize(sbt_field_icon0);
1165 if(sbt_fixcolumnwidth_iconlen < f)
1166 sbt_fixcolumnwidth_iconlen = f;
1169 if(sbt_field_icon1 != "")
1171 sz = draw_getimagesize(sbt_field_icon1);
1173 if(sbt_fixcolumnwidth_iconlen < f)
1174 sbt_fixcolumnwidth_iconlen = f;
1177 if(sbt_field_icon2 != "")
1179 sz = draw_getimagesize(sbt_field_icon2);
1181 if(sbt_fixcolumnwidth_iconlen < f)
1182 sbt_fixcolumnwidth_iconlen = f;
1185 if(sbt_fixcolumnwidth_iconlen != 0)
1187 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1188 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1191 sbt_fixcolumnwidth_marginlen = 0;
1193 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1196 float remaining_space = 0;
1197 for(j = 0; j < sbt_num_fields; ++j)
1199 if (sbt_field[i] != SP_SEPARATOR)
1200 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1201 sbt_field_size[i] = panel_size.x - remaining_space;
1203 if (sbt_fixcolumnwidth_iconlen != 0)
1204 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1205 float namesize = panel_size.x - remaining_space;
1206 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1207 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1209 max_namesize = vid_conwidth - remaining_space;
1212 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1214 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1215 if(sbt_field_size[i] < f)
1216 sbt_field_size[i] = f;
1221 void Scoreboard_initFieldSizes()
1223 for(int i = 0; i < sbt_num_fields; ++i)
1225 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1226 Scoreboard_FixColumnWidth(i, "");
1230 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1233 vector column_dim = eY * panel_size.y;
1235 column_dim.y -= 1.25 * hud_fontsize.y;
1236 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1237 pos.x += hud_fontsize.x * 0.5;
1238 for(i = 0; i < sbt_num_fields; ++i)
1240 if(sbt_field[i] == SP_SEPARATOR)
1242 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1245 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1246 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1247 pos.x += column_dim.x;
1249 if(sbt_field[i] == SP_SEPARATOR)
1251 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1252 for(i = sbt_num_fields - 1; i > 0; --i)
1254 if(sbt_field[i] == SP_SEPARATOR)
1257 pos.x -= sbt_field_size[i];
1262 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1263 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1266 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1267 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1268 pos.x -= hud_fontsize.x;
1272 pos.x = panel_pos.x;
1273 pos.y += 1.25 * hud_fontsize.y;
1277 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1279 TC(bool, is_self); TC(int, pl_number);
1281 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1283 vector h_pos = item_pos;
1284 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1285 // alternated rows highlighting
1286 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1288 if (pl == scoreboard_selected_player)
1289 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1292 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1293 else if((sbt_highlight) && (!(pl_number % 2)))
1294 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1296 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1298 vector pos = item_pos;
1299 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1301 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1303 pos.x += hud_fontsize.x * 0.5;
1304 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1305 vector tmp = '0 0 0';
1307 PlayerScoreField field;
1308 for(i = 0; i < sbt_num_fields; ++i)
1310 field = sbt_field[i];
1311 if(field == SP_SEPARATOR)
1314 if(is_spec && field != SP_NAME && field != SP_PING) {
1315 pos.x += sbt_field_size[i] + hud_fontsize.x;
1318 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1319 str = Scoreboard_FixColumnWidth(i, str);
1321 pos.x += sbt_field_size[i] + hud_fontsize.x;
1323 if(field == SP_NAME) {
1324 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1325 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1327 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1328 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1331 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1332 if(sbt_field_icon0 != "")
1333 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1334 if(sbt_field_icon1 != "")
1335 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1336 if(sbt_field_icon2 != "")
1337 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1340 if(sbt_field[i] == SP_SEPARATOR)
1342 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1343 for(i = sbt_num_fields-1; i > 0; --i)
1345 field = sbt_field[i];
1346 if(field == SP_SEPARATOR)
1349 if(is_spec && field != SP_NAME && field != SP_PING) {
1350 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1354 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1355 str = Scoreboard_FixColumnWidth(i, str);
1357 if(field == SP_NAME) {
1358 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1359 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1361 tmp.x = sbt_fixcolumnwidth_len;
1362 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1365 tmp.x = sbt_field_size[i];
1366 if(sbt_field_icon0 != "")
1367 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1368 if(sbt_field_icon1 != "")
1369 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1370 if(sbt_field_icon2 != "")
1371 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1372 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1377 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1380 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1383 vector h_pos = item_pos;
1384 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1386 bool complete = (this_team == NUM_SPECTATOR);
1389 if((sbt_highlight) && (!(pl_number % 2)))
1390 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1392 vector pos = item_pos;
1393 pos.x += hud_fontsize.x * 0.5;
1394 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1396 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1398 width_limit -= stringwidth("...", false, hud_fontsize);
1399 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1400 static float max_name_width = 0;
1402 float fieldsize = 0;
1403 float min_fieldsize = 0;
1404 float fieldpadding = hud_fontsize.x * 0.25;
1405 if(this_team == NUM_SPECTATOR)
1407 if(autocvar_hud_panel_scoreboard_spectators_showping)
1408 min_fieldsize = stringwidth("999", false, hud_fontsize);
1410 else if(autocvar_hud_panel_scoreboard_others_showscore)
1411 min_fieldsize = stringwidth("99", false, hud_fontsize);
1412 for(i = 0; pl; pl = pl.sort_next)
1414 if(pl.team != this_team)
1416 if(pl == ignored_pl)
1420 if(this_team == NUM_SPECTATOR)
1422 if(autocvar_hud_panel_scoreboard_spectators_showping)
1423 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1425 else if(autocvar_hud_panel_scoreboard_others_showscore)
1426 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1428 string str = entcs_GetName(pl.sv_entnum);
1429 if (autocvar_hud_panel_scoreboard_playerid)
1430 str = Scoreboard_AddPlayerId(str, pl);
1431 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1432 float column_width = stringwidth(str, true, hud_fontsize);
1433 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1435 if(column_width > max_name_width)
1436 max_name_width = column_width;
1437 column_width = max_name_width;
1441 fieldsize = stringwidth(field, false, hud_fontsize);
1442 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1445 if(pos.x + column_width > width_limit)
1450 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1455 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1456 pos.y += hud_fontsize.y * 1.25;
1460 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1462 if (pl == scoreboard_selected_player)
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, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1470 vector name_pos = pos;
1471 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1472 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1473 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1476 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1477 h_size.y = hud_fontsize.y;
1478 vector field_pos = pos;
1479 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1480 field_pos.x += column_width - h_size.x;
1482 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1483 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1484 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
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, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1492 pos.x += column_width;
1493 pos.x += hud_fontsize.x;
1495 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1498 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1500 int max_players = 999;
1501 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1503 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1506 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1507 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1508 height /= team_count;
1511 height -= panel_bg_padding * 2; // - padding
1512 max_players = floor(height / (hud_fontsize.y * 1.25));
1513 if(max_players <= 1)
1515 if(max_players == tm.team_size)
1520 entity me = playerslots[current_player];
1522 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1523 panel_size.y += panel_bg_padding * 2;
1525 vector scoreboard_selected_hl_pos = pos;
1526 vector scoreboard_selected_hl_size = '0 0 0';
1527 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1528 scoreboard_selected_hl_size.y = panel_size.y;
1532 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1533 if(panel.current_panel_bg != "0")
1534 end_pos.y += panel_bg_border * 2;
1536 if(panel_bg_padding)
1538 panel_pos += '1 1 0' * panel_bg_padding;
1539 panel_size -= '2 2 0' * panel_bg_padding;
1543 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1547 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1549 pos.y += 1.25 * hud_fontsize.y;
1552 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1554 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1557 // print header row and highlight columns
1558 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1560 // fill the table and draw the rows
1561 bool is_self = false;
1562 bool self_shown = false;
1564 for(pl = players.sort_next; pl; pl = pl.sort_next)
1566 if(pl.team != tm.team)
1568 if(i == max_players - 2 && pl != me)
1570 if(!self_shown && me.team == tm.team)
1572 Scoreboard_DrawItem(pos, rgb, me, true, i);
1574 pos.y += 1.25 * hud_fontsize.y;
1578 if(i >= max_players - 1)
1580 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1583 is_self = (pl.sv_entnum == current_player);
1584 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1587 pos.y += 1.25 * hud_fontsize.y;
1591 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1593 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1595 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1596 _alpha *= panel_fg_alpha;
1598 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1602 panel_size.x += panel_bg_padding * 2; // restore initial width
1606 bool Scoreboard_WouldDraw()
1608 if (scoreboard_ui_enabled)
1610 if (scoreboard_ui_disabling)
1612 if (scoreboard_fade_alpha == 0)
1613 HUD_Scoreboard_UI_Disable_Instantly();
1616 if (intermission && scoreboard_ui_enabled == 2)
1618 HUD_Scoreboard_UI_Disable_Instantly();
1623 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1625 else if (QuickMenu_IsOpened())
1627 else if (HUD_Radar_Clickable())
1629 else if (sb_showscores) // set by +showscores engine command
1631 else if (intermission == 1)
1633 else if (intermission == 2)
1635 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1636 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1640 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1645 float average_accuracy;
1646 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1648 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1650 WepSet weapons_stat = WepSet_GetFromStat();
1651 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1652 int disownedcnt = 0;
1654 FOREACH(Weapons, it != WEP_Null, {
1655 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1657 WepSet set = it.m_wepset;
1658 if(it.spawnflags & WEP_TYPE_OTHER)
1663 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1665 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1672 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1673 if (weapon_cnt <= 0) return pos;
1676 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1678 int columns = ceil(weapon_cnt / rows);
1680 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1681 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1682 float height = weapon_height + hud_fontsize.y;
1684 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);
1685 pos.y += 1.25 * hud_fontsize.y;
1686 if(panel.current_panel_bg != "0")
1687 pos.y += panel_bg_border;
1690 panel_size.y = height * rows;
1691 panel_size.y += panel_bg_padding * 2;
1693 float panel_bg_alpha_save = panel_bg_alpha;
1694 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1696 panel_bg_alpha = panel_bg_alpha_save;
1698 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1699 if(panel.current_panel_bg != "0")
1700 end_pos.y += panel_bg_border * 2;
1702 if(panel_bg_padding)
1704 panel_pos += '1 1 0' * panel_bg_padding;
1705 panel_size -= '2 2 0' * panel_bg_padding;
1709 vector tmp = panel_size;
1711 float weapon_width = tmp.x / columns / rows;
1714 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1718 // column highlighting
1719 for (int i = 0; i < columns; ++i)
1721 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);
1724 for (int i = 0; i < rows; ++i)
1725 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1728 average_accuracy = 0;
1729 int weapons_with_stats = 0;
1731 pos.x += weapon_width / 2;
1733 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1736 Accuracy_LoadColors();
1738 float oldposx = pos.x;
1742 FOREACH(Weapons, it != WEP_Null, {
1743 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1745 WepSet set = it.m_wepset;
1746 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1748 if (it.spawnflags & WEP_TYPE_OTHER)
1752 if (weapon_stats >= 0)
1753 weapon_alpha = sbt_fg_alpha;
1755 weapon_alpha = 0.2 * sbt_fg_alpha;
1758 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1760 if (weapon_stats >= 0) {
1761 weapons_with_stats += 1;
1762 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1764 string s = sprintf("%d%%", weapon_stats * 100);
1765 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1767 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1768 rgb = Accuracy_GetColor(weapon_stats);
1770 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1772 tmpos.x += weapon_width * rows;
1773 pos.x += weapon_width * rows;
1774 if (rows == 2 && column == columns - 1) {
1782 if (weapons_with_stats)
1783 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1785 panel_size.x += panel_bg_padding * 2; // restore initial width
1790 bool is_item_filtered(entity it)
1792 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1794 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1797 if (it.instanceOfArmor || it.instanceOfHealth)
1799 int ha_mask = floor(mask) % 10;
1802 default: return false;
1803 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1804 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1805 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1806 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1809 if (it.instanceOfAmmo)
1811 int ammo_mask = floor(mask / 10) % 10;
1812 return (ammo_mask == 1);
1817 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1819 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1821 int disowned_cnt = 0;
1822 int uninteresting_cnt = 0;
1823 IL_EACH(default_order_items, true, {
1824 int q = g_inventory.inv_items[it.m_id];
1825 //q = 1; // debug: display all items
1826 if (is_item_filtered(it))
1827 ++uninteresting_cnt;
1831 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1832 int n = items_cnt - disowned_cnt;
1833 if (n <= 0) return pos;
1835 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1836 int columns = max(6, ceil(n / rows));
1838 float item_height = hud_fontsize.y * 2.3;
1839 float height = item_height + hud_fontsize.y;
1841 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1842 pos.y += 1.25 * hud_fontsize.y;
1843 if(panel.current_panel_bg != "0")
1844 pos.y += panel_bg_border;
1847 panel_size.y = height * rows;
1848 panel_size.y += panel_bg_padding * 2;
1850 float panel_bg_alpha_save = panel_bg_alpha;
1851 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1853 panel_bg_alpha = panel_bg_alpha_save;
1855 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1856 if(panel.current_panel_bg != "0")
1857 end_pos.y += panel_bg_border * 2;
1859 if(panel_bg_padding)
1861 panel_pos += '1 1 0' * panel_bg_padding;
1862 panel_size -= '2 2 0' * panel_bg_padding;
1866 vector tmp = panel_size;
1868 float item_width = tmp.x / columns / rows;
1871 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1875 // column highlighting
1876 for (int i = 0; i < columns; ++i)
1878 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);
1881 for (int i = 0; i < rows; ++i)
1882 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1886 pos.x += item_width / 2;
1888 float oldposx = pos.x;
1892 IL_EACH(default_order_items, !is_item_filtered(it), {
1893 int n = g_inventory.inv_items[it.m_id];
1894 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1895 if (n <= 0) continue;
1896 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);
1898 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1899 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1900 tmpos.x += item_width * rows;
1901 pos.x += item_width * rows;
1902 if (rows == 2 && column == columns - 1) {
1910 panel_size.x += panel_bg_padding * 2; // restore initial width
1915 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1917 pos.x += hud_fontsize.x * 0.25;
1918 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1919 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1920 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1922 pos.y += hud_fontsize.y;
1927 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1928 float stat_secrets_found, stat_secrets_total;
1929 float stat_monsters_killed, stat_monsters_total;
1933 // get monster stats
1934 stat_monsters_killed = STAT(MONSTERS_KILLED);
1935 stat_monsters_total = STAT(MONSTERS_TOTAL);
1937 // get secrets stats
1938 stat_secrets_found = STAT(SECRETS_FOUND);
1939 stat_secrets_total = STAT(SECRETS_TOTAL);
1941 // get number of rows
1942 if(stat_secrets_total)
1944 if(stat_monsters_total)
1947 // if no rows, return
1951 // draw table header
1952 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1953 pos.y += 1.25 * hud_fontsize.y;
1954 if(panel.current_panel_bg != "0")
1955 pos.y += panel_bg_border;
1958 panel_size.y = hud_fontsize.y * rows;
1959 panel_size.y += panel_bg_padding * 2;
1962 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1963 if(panel.current_panel_bg != "0")
1964 end_pos.y += panel_bg_border * 2;
1966 if(panel_bg_padding)
1968 panel_pos += '1 1 0' * panel_bg_padding;
1969 panel_size -= '2 2 0' * panel_bg_padding;
1973 vector tmp = panel_size;
1976 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1979 if(stat_monsters_total)
1981 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1982 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1986 if(stat_secrets_total)
1988 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1989 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1992 panel_size.x += panel_bg_padding * 2; // restore initial width
1996 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1999 RANKINGS_RECEIVED_CNT = 0;
2000 for (i=RANKINGS_CNT-1; i>=0; --i)
2002 ++RANKINGS_RECEIVED_CNT;
2004 if (RANKINGS_RECEIVED_CNT == 0)
2007 vector hl_rgb = rgb + '0.5 0.5 0.5';
2009 vector scoreboard_selected_hl_pos = pos;
2011 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2012 pos.y += 1.25 * hud_fontsize.y;
2013 if(panel.current_panel_bg != "0")
2014 pos.y += panel_bg_border;
2016 vector scoreboard_selected_hl_size = '0 0 0';
2017 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2018 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2023 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2025 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2030 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2032 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2036 float ranksize = 3 * hud_fontsize.x;
2037 float timesize = 5 * hud_fontsize.x;
2038 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2039 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2040 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2043 rankings_cnt = RANKINGS_RECEIVED_CNT;
2044 rankings_rows = ceil(rankings_cnt / rankings_columns);
2047 // expand name column to fill the entire row
2048 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2049 namesize += available_space;
2050 columnsize.x += available_space;
2052 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2053 panel_size.y += panel_bg_padding * 2;
2054 scoreboard_selected_hl_size.y += panel_size.y;
2058 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2059 if(panel.current_panel_bg != "0")
2060 end_pos.y += panel_bg_border * 2;
2062 if(panel_bg_padding)
2064 panel_pos += '1 1 0' * panel_bg_padding;
2065 panel_size -= '2 2 0' * panel_bg_padding;
2071 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2073 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2075 int column = 0, j = 0;
2076 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2077 int start_item = rankings_start_column * rankings_rows;
2078 for(i = start_item; i < start_item + rankings_cnt; ++i)
2080 int t = grecordtime[i];
2084 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2085 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2086 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2087 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2089 str = count_ordinal(i+1);
2090 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2091 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2092 str = ColorTranslateRGB(grecordholder[i]);
2094 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2095 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2097 pos.y += 1.25 * hud_fontsize.y;
2099 if(j >= rankings_rows)
2103 pos.x += panel_size.x / rankings_columns;
2104 pos.y = panel_pos.y;
2107 strfree(zoned_name_self);
2109 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2111 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2112 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2115 panel_size.x += panel_bg_padding * 2; // restore initial width
2119 bool have_weapon_stats;
2120 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2122 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2124 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2127 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2128 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2134 if (!have_weapon_stats)
2136 FOREACH(Weapons, it != WEP_Null, {
2137 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2138 if (weapon_stats >= 0)
2140 have_weapon_stats = true;
2144 if (!have_weapon_stats)
2151 bool have_item_stats;
2152 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2154 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2156 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2159 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2160 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2166 if (!have_item_stats)
2168 IL_EACH(default_order_items, true, {
2169 if (!is_item_filtered(it))
2171 int q = g_inventory.inv_items[it.m_id];
2172 //q = 1; // debug: display all items
2175 have_item_stats = true;
2180 if (!have_item_stats)
2187 vector Scoreboard_Spectators_Draw(vector pos) {
2192 for(pl = players.sort_next; pl; pl = pl.sort_next)
2194 if(pl.team == NUM_SPECTATOR)
2196 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2197 if(tm.team == NUM_SPECTATOR)
2199 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2200 draw_beginBoldFont();
2201 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2203 pos.y += 1.25 * hud_fontsize.y;
2205 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2206 pos.y += 1.25 * hud_fontsize.y;
2211 if (str != "") // if there's at least one spectator
2212 pos.y += 0.5 * hud_fontsize.y;
2217 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2219 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2220 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2221 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2222 (s_label == "score") ? CTX(_("SCO^points")) :
2223 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2226 void Scoreboard_Draw()
2228 if(!autocvar__hud_configure)
2230 if(!hud_draw_maximized) return;
2232 // frametime checks allow to toggle the scoreboard even when the game is paused
2233 if(scoreboard_active) {
2234 if (scoreboard_fade_alpha == 0)
2235 scoreboard_time = time;
2236 if(hud_configure_menu_open == 1)
2237 scoreboard_fade_alpha = 1;
2238 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2239 if (scoreboard_fadeinspeed && frametime)
2240 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2242 scoreboard_fade_alpha = 1;
2243 if(hud_fontsize_str != autocvar_hud_fontsize)
2245 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2246 Scoreboard_initFieldSizes();
2247 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2251 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2252 if (scoreboard_fadeoutspeed && frametime)
2253 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2255 scoreboard_fade_alpha = 0;
2258 if (!scoreboard_fade_alpha)
2260 scoreboard_acc_fade_alpha = 0;
2261 scoreboard_itemstats_fade_alpha = 0;
2266 scoreboard_fade_alpha = 0;
2268 if (autocvar_hud_panel_scoreboard_dynamichud)
2271 HUD_Scale_Disable();
2273 if(scoreboard_fade_alpha <= 0)
2275 panel_fade_alpha *= scoreboard_fade_alpha;
2276 HUD_Panel_LoadCvars();
2278 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2279 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2280 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2281 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2282 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2283 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2284 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2286 // don't overlap with con_notify
2287 if(!autocvar__hud_configure)
2288 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2290 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2291 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2292 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2293 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2294 panel_pos.x = scoreboard_left;
2295 panel_size.x = fixed_scoreboard_width;
2297 Scoreboard_UpdatePlayerTeams();
2299 scoreboard_top = panel_pos.y;
2300 vector pos = panel_pos;
2305 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2307 // Begin of Game Info Section
2308 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2309 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2311 // Game Info: Game Type
2312 if (scoreboard_ui_enabled == 2)
2313 str = _("Team Selection");
2315 str = MapInfo_Type_ToText(gametype);
2316 draw_beginBoldFont();
2317 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);
2320 pos.y += sb_gameinfo_type_fontsize.y;
2321 // Game Info: Game Detail
2322 if (scoreboard_ui_enabled == 2)
2324 if (scoreboard_selected_team)
2325 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2327 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2328 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);
2330 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2331 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2332 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);
2336 float tl = STAT(TIMELIMIT);
2337 float fl = STAT(FRAGLIMIT);
2338 float ll = STAT(LEADLIMIT);
2339 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2342 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2343 if(!gametype.m_hidelimits)
2348 str = strcat(str, "^7 / "); // delimiter
2349 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2353 if(tl > 0 || fl > 0)
2356 if (ll_and_fl && fl > 0)
2357 str = strcat(str, "^7 & ");
2359 str = strcat(str, "^7 / ");
2361 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2364 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
2365 // map name and player count
2369 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2370 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2371 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2373 // End of Game Info Section
2375 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2376 if(panel.current_panel_bg != "0")
2377 pos.y += panel_bg_border;
2379 // Draw the scoreboard
2380 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2383 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2387 vector panel_bg_color_save = panel_bg_color;
2388 vector team_score_baseoffset;
2389 vector team_size_baseoffset;
2390 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2392 // put team score to the left of scoreboard (and team size to the right)
2393 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2394 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2395 if(panel.current_panel_bg != "0")
2397 team_score_baseoffset.x -= panel_bg_border;
2398 team_size_baseoffset.x += panel_bg_border;
2403 // put team score to the right of scoreboard (and team size to the left)
2404 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2405 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2406 if(panel.current_panel_bg != "0")
2408 team_score_baseoffset.x += panel_bg_border;
2409 team_size_baseoffset.x -= panel_bg_border;
2413 int team_size_total = 0;
2414 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2416 // calculate team size total (sum of all team sizes)
2417 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2418 if(tm.team != NUM_SPECTATOR)
2419 team_size_total += tm.team_size;
2422 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2424 if(tm.team == NUM_SPECTATOR)
2429 draw_beginBoldFont();
2430 vector rgb = Team_ColorRGB(tm.team);
2431 str = ftos(tm.(teamscores(ts_primary)));
2432 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2434 // team score on the left (default)
2435 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2439 // team score on the right
2440 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2442 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2444 // team size (if set to show on the side)
2445 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2447 // calculate the starting position for the whole team size info string
2448 str = sprintf("%d/%d", tm.team_size, team_size_total);
2449 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2451 // team size on the left
2452 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2456 // team size on the right
2457 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2459 str = sprintf("%d", tm.team_size);
2460 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2461 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2462 str = sprintf("/%d", team_size_total);
2463 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2467 // secondary score, e.g. keyhunt
2468 if(ts_primary != ts_secondary)
2470 str = ftos(tm.(teamscores(ts_secondary)));
2471 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2474 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2479 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2482 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2485 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2486 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2487 else if(panel_bg_color_team > 0)
2488 panel_bg_color = rgb * panel_bg_color_team;
2490 panel_bg_color = rgb;
2491 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2493 panel_bg_color = panel_bg_color_save;
2497 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2498 if(tm.team != NUM_SPECTATOR)
2501 // display it anyway
2502 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2505 // draw scoreboard spectators before accuracy and item stats
2506 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2507 pos = Scoreboard_Spectators_Draw(pos);
2510 // draw accuracy and item stats
2511 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2512 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2513 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2514 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2516 // draw scoreboard spectators after accuracy and item stats and before rankings
2517 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2518 pos = Scoreboard_Spectators_Draw(pos);
2521 if(MUTATOR_CALLHOOK(ShowRankings)) {
2522 string ranktitle = M_ARGV(0, string);
2523 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2524 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2525 if(race_speedaward_alltimebest)
2528 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2532 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2533 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2534 str = strcat(str, " / ");
2536 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2537 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2538 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2539 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2541 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2546 // draw scoreboard spectators after rankings
2547 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2548 pos = Scoreboard_Spectators_Draw(pos);
2551 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2553 // draw scoreboard spectators after mapstats
2554 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2555 pos = Scoreboard_Spectators_Draw(pos);
2559 // print information about respawn status
2560 float respawn_time = STAT(RESPAWN_TIME);
2561 if(!intermission && respawn_time)
2563 if(respawn_time < 0)
2565 // a negative number means we are awaiting respawn, time value is still the same
2566 respawn_time *= -1; // remove mark now that we checked it
2568 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2569 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2571 str = sprintf(_("^1Respawning in ^3%s^1..."),
2572 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2573 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2575 count_seconds(ceil(respawn_time - time))
2579 else if(time < respawn_time)
2581 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2582 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2583 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2585 count_seconds(ceil(respawn_time - time))
2589 else if(time >= respawn_time)
2590 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2592 pos.y += 1.2 * hud_fontsize.y;
2593 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2596 pos.y += hud_fontsize.y;
2597 if (scoreboard_fade_alpha < 1)
2598 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2599 else if (pos.y != scoreboard_bottom)
2601 if (pos.y > scoreboard_bottom)
2602 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2604 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2609 if (scoreboard_fade_alpha == 1)
2611 if (scoreboard_bottom > 0.95 * vid_conheight)
2612 rankings_rows = max(1, rankings_rows - 1);
2613 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2614 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2616 rankings_cnt = rankings_rows * rankings_columns;