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 = " ";
110 float scoreboard_time;
112 // mode 0: returns translated label
113 // mode 1: prints name and description of all the labels
114 string Label_getInfo(string label, int mode)
117 label = "bckills"; // first case in the switch
121 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
122 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
123 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")));
124 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
125 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
126 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
127 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
128 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
129 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
130 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
131 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
132 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
133 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
134 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
135 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
136 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
137 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
138 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
139 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
140 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
141 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
142 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
143 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
144 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
145 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
146 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
147 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
148 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")));
149 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
150 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
151 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
152 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
153 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
154 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
155 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
156 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
157 case "avgspeed": if (!mode) return CTX(_("SCO^average speed"));else LOG_HELP(strcat("^3", "avgspeed", " ^7", _("Average speed (CTS)")));
158 case "topspeed": if (!mode) return CTX(_("SCO^top speed")); else LOG_HELP(strcat("^3", "topspeed", " ^7", _("Top speed (CTS)")));
159 case "startspeed": if (!mode) return CTX(_("SCO^start speed")); else LOG_HELP(strcat("^3", "startspeed", " ^7", _("Start speed (CTS)")));
160 case "strafe": if (!mode) return CTX(_("SCO^strafe")); else LOG_HELP(strcat("^3", "strafe", " ^7", _("Strafe efficiency (CTS)")));
161 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
162 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
163 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
164 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
165 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
166 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
167 default: return label;
172 bool scoreboard_ui_disabling;
173 void HUD_Scoreboard_UI_Disable()
175 scoreboard_ui_disabling = true;
176 sb_showscores = false;
179 void HUD_Scoreboard_UI_Disable_Instantly()
181 scoreboard_ui_disabling = false;
182 scoreboard_ui_enabled = 0;
183 scoreboard_selected_panel = 0;
184 scoreboard_selected_player = NULL;
185 scoreboard_selected_team = NULL;
188 // mode: 0 normal, 1 team selection
189 void Scoreboard_UI_Enable(int mode)
195 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
198 // release player's pressed keys as they aren't released elsewhere
199 // in particular jump needs to be released as it may open the team selection
200 // (when server detects jump has been pressed it sends the command to open the team selection)
201 Release_Common_Keys();
202 scoreboard_ui_enabled = 2;
203 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
207 if (scoreboard_ui_enabled == 1)
209 scoreboard_ui_enabled = 1;
210 scoreboard_selected_panel = SB_PANEL_FIRST;
212 scoreboard_selected_player = NULL;
213 scoreboard_selected_team = NULL;
214 scoreboard_selected_panel_time = time;
217 int rankings_start_column;
218 int rankings_rows = 0;
219 int rankings_columns = 0;
220 int rankings_cnt = 0;
221 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
225 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
230 mousepos.x = nPrimary;
231 mousepos.y = nSecondary;
238 // at this point bInputType can only be 0 or 1 (key pressed or released)
239 bool key_pressed = (bInputType == 0);
241 // ESC to exit (TAB-ESC works too)
242 if(nPrimary == K_ESCAPE)
246 HUD_Scoreboard_UI_Disable();
250 // block any input while a menu dialog is fading
251 if(autocvar__menu_alpha)
257 // allow console bind to work
258 string con_keys = findkeysforcommand("toggleconsole", 0);
259 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
261 bool hit_con_bind = false;
263 for (i = 0; i < keys; ++i)
265 if(nPrimary == stof(argv(i)))
270 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
271 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
272 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
273 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
276 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
277 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
278 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
279 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
282 if(nPrimary == K_TAB)
286 if (scoreboard_ui_enabled == 2)
288 if (hudShiftState & S_SHIFT)
291 goto downarrow_action;
294 if (hudShiftState & S_SHIFT)
296 --scoreboard_selected_panel;
297 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
298 --scoreboard_selected_panel;
299 if (scoreboard_selected_panel < SB_PANEL_FIRST)
300 scoreboard_selected_panel = SB_PANEL_MAX;
304 ++scoreboard_selected_panel;
305 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
306 ++scoreboard_selected_panel;
307 if (scoreboard_selected_panel > SB_PANEL_MAX)
308 scoreboard_selected_panel = SB_PANEL_FIRST;
311 scoreboard_selected_panel_time = time;
313 else if(nPrimary == K_DOWNARROW)
317 LABEL(downarrow_action);
318 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
320 if (scoreboard_ui_enabled == 2)
322 entity curr_team = NULL;
323 bool scoreboard_selected_team_found = false;
324 if (!scoreboard_selected_team)
325 scoreboard_selected_team_found = true;
327 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
329 if(tm.team == NUM_SPECTATOR)
332 if (scoreboard_selected_team_found)
334 if (scoreboard_selected_team == tm)
335 scoreboard_selected_team_found = true;
338 if (curr_team == scoreboard_selected_team) // loop reached the last team
340 scoreboard_selected_team = curr_team;
345 entity curr_pl = NULL;
346 bool scoreboard_selected_player_found = false;
347 if (!scoreboard_selected_player)
348 scoreboard_selected_player_found = true;
350 for(tm = teams.sort_next; tm; tm = tm.sort_next)
352 if(tm.team != NUM_SPECTATOR)
353 for(pl = players.sort_next; pl; pl = pl.sort_next)
355 if(pl.team != tm.team)
358 if (scoreboard_selected_player_found)
360 if (scoreboard_selected_player == pl)
361 scoreboard_selected_player_found = true;
365 if (curr_pl == scoreboard_selected_player) // loop reached the last player
367 scoreboard_selected_player = curr_pl;
371 else if(nPrimary == K_UPARROW)
375 LABEL(uparrow_action);
376 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
378 if (scoreboard_ui_enabled == 2)
380 entity prev_team = NULL;
381 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
383 if(tm.team == NUM_SPECTATOR)
385 if (tm == scoreboard_selected_team)
390 scoreboard_selected_team = prev_team;
394 entity prev_pl = NULL;
396 for(tm = teams.sort_next; tm; tm = tm.sort_next)
398 if(tm.team != NUM_SPECTATOR)
399 for(pl = players.sort_next; pl; pl = pl.sort_next)
401 if(pl.team != tm.team)
403 if (pl == scoreboard_selected_player)
409 scoreboard_selected_player = prev_pl;
413 else if(nPrimary == K_RIGHTARROW)
417 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
418 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
420 else if(nPrimary == K_LEFTARROW)
424 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
425 rankings_start_column = max(rankings_start_column - 1, 0);
427 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
431 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
433 if (scoreboard_ui_enabled == 2)
436 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
439 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
440 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
441 HUD_Scoreboard_UI_Disable();
443 else if (scoreboard_selected_player)
444 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
447 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
451 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
453 switch (scoreboard_selected_columns_layout)
456 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
458 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
459 scoreboard_selected_columns_layout = 1;
464 localcmd(sprintf("scoreboard_columns_set default\n"));
465 scoreboard_selected_columns_layout = 2;
468 localcmd(sprintf("scoreboard_columns_set all\n"));
469 scoreboard_selected_columns_layout = 0;
474 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
478 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
480 if (scoreboard_selected_player)
482 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
483 HUD_Scoreboard_UI_Disable();
487 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
491 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
493 if (scoreboard_selected_player)
494 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
497 else if(hit_con_bind || nPrimary == K_PAUSE)
503 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
504 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
506 void Scoreboard_InitScores()
510 ps_primary = ps_secondary = NULL;
511 ts_primary = ts_secondary = -1;
512 FOREACH(Scores, true, {
513 if(scores_flags(it) & SFL_NOT_SORTABLE)
515 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
516 if(f == SFL_SORT_PRIO_PRIMARY)
518 if(f == SFL_SORT_PRIO_SECONDARY)
521 if(ps_secondary == NULL)
522 ps_secondary = ps_primary;
524 for(i = 0; i < MAX_TEAMSCORE; ++i)
526 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
527 if(f == SFL_SORT_PRIO_PRIMARY)
529 if(f == SFL_SORT_PRIO_SECONDARY)
532 if(ts_secondary == -1)
533 ts_secondary = ts_primary;
535 Cmd_Scoreboard_SetFields(0);
539 void Scoreboard_UpdatePlayerTeams()
541 static float update_time;
542 if (time <= update_time)
549 for(pl = players.sort_next; pl; pl = pl.sort_next)
551 numplayers += pl.team != NUM_SPECTATOR;
553 int Team = entcs_GetScoreTeam(pl.sv_entnum);
554 if(SetTeam(pl, Team))
557 Scoreboard_UpdatePlayerPos(pl);
561 pl = players.sort_next;
566 print(strcat("PNUM: ", ftos(num), "\n"));
571 int Scoreboard_CompareScore(int vl, int vr, int f)
573 TC(int, vl); TC(int, vr); TC(int, f);
574 if(f & SFL_ZERO_IS_WORST)
576 if(vl == 0 && vr != 0)
578 if(vl != 0 && vr == 0)
582 return IS_INCREASING(f);
584 return IS_DECREASING(f);
588 float Scoreboard_ComparePlayerScores(entity left, entity right)
590 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
591 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
598 if(vl == NUM_SPECTATOR)
600 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
602 if(!left.gotscores && right.gotscores)
607 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
608 if (res >= 0) return res;
610 if (ps_secondary && ps_secondary != ps_primary)
612 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
613 if (res >= 0) return res;
616 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
617 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
618 if (res >= 0) return res;
621 if (left.sv_entnum < right.sv_entnum)
627 void Scoreboard_UpdatePlayerPos(entity player)
630 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
632 SORT_SWAP(player, ent);
634 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
636 SORT_SWAP(ent, player);
640 float Scoreboard_CompareTeamScores(entity left, entity right)
642 if(left.team == NUM_SPECTATOR)
644 if(right.team == NUM_SPECTATOR)
649 for(int i = -2; i < MAX_TEAMSCORE; ++i)
653 if (fld_idx == -1) fld_idx = ts_primary;
654 else if (ts_secondary == ts_primary) continue;
655 else fld_idx = ts_secondary;
660 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
663 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
664 if (r >= 0) return r;
667 if (left.team < right.team)
673 void Scoreboard_UpdateTeamPos(entity Team)
676 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
678 SORT_SWAP(Team, ent);
680 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
682 SORT_SWAP(ent, Team);
686 void Cmd_Scoreboard_Help()
688 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
689 LOG_HELP(_("Usage:"));
690 LOG_HELP("^2scoreboard_columns_set ^3default");
691 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
692 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
693 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
694 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
695 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
696 LOG_HELP(_("The following field names are recognized (case insensitive):"));
702 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
703 "of game types, then a slash, to make the field show up only in these\n"
704 "or in all but these game types. You can also specify 'all' as a\n"
705 "field to show all fields available for the current game mode."));
708 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
709 "include/exclude ALL teams/noteams game modes."));
712 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
713 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
714 "right of the vertical bar aligned to the right."));
715 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
716 "other gamemodes except DM."));
719 // NOTE: adding a gametype with ? to not warn for an optional field
720 // make sure it's excluded in a previous exclusive rule, if any
721 // otherwise the previous exclusive rule warns anyway
722 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
723 #define SCOREBOARD_DEFAULT_COLUMNS \
724 "ping pl fps name |" \
725 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
726 " -teams,lms/deaths +ft,tdm/deaths" \
728 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
729 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
730 " +tdm,ft,dom,ons,as/teamkills"\
731 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
732 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
733 " +lms/lives +lms/rank" \
734 " +kh/kckills +kh/losses +kh/caps" \
735 " ?+rc/laps ?+rc/time ?+cts/strafe ?+cts/startspeed ?+cts/avgspeed ?+cts/topspeed +rc,cts/fastest" \
736 " +as/objectives +nb/faults +nb/goals" \
737 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
738 " +dom/ticks +dom/takes" \
739 " -lms,rc,cts,inv,nb/score"
741 void Cmd_Scoreboard_SetFields(int argc)
746 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
750 return; // do nothing, we don't know gametype and scores yet
752 // sbt_fields uses strunzone on the titles!
753 if(!sbt_field_title[0])
754 for(i = 0; i < MAX_SBT_FIELDS; ++i)
755 sbt_field_title[i] = strzone("(null)");
757 // TODO: re enable with gametype dependant cvars?
758 if(argc < 3) // no arguments provided
759 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
762 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
766 if(argv(2) == "default" || argv(2) == "expand_default")
768 if(argv(2) == "expand_default")
769 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
770 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
772 else if(argv(2) == "all" || argv(2) == "ALL")
774 string s = "ping pl name |"; // scores without label (not really scores)
777 // scores without label
778 s = strcat(s, " ", "sum");
779 s = strcat(s, " ", "kdratio");
780 s = strcat(s, " ", "frags");
782 FOREACH(Scores, true, {
784 if(it != ps_secondary)
785 if(scores_label(it) != "")
786 s = strcat(s, " ", scores_label(it));
788 if(ps_secondary != ps_primary)
789 s = strcat(s, " ", scores_label(ps_secondary));
790 s = strcat(s, " ", scores_label(ps_primary));
791 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
798 hud_fontsize = HUD_GetFontsize("hud_fontsize");
800 for(i = 1; i < argc - 1; ++i)
803 bool nocomplain = false;
804 if(substring(str, 0, 1) == "?")
807 str = substring(str, 1, strlen(str) - 1);
810 slash = strstrofs(str, "/", 0);
813 pattern = substring(str, 0, slash);
814 str = substring(str, slash + 1, strlen(str) - (slash + 1));
816 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
820 str = strtolower(str);
821 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
822 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
827 // fields without a label (not networked via the score system)
828 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
829 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
830 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
831 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
832 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
833 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
834 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
835 default: // fields with a label
837 // map alternative labels
838 if (str == "damage") str = "dmg";
839 if (str == "damagetaken") str = "dmgtaken";
841 FOREACH(Scores, true, {
842 if (str == strtolower(scores_label(it))) {
844 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
848 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
849 if(!nocomplain && str != "fps") // server can disable the fps field
850 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
852 strfree(sbt_field_title[sbt_num_fields]);
853 sbt_field_size[sbt_num_fields] = 0;
857 sbt_field[sbt_num_fields] = j;
860 if(j == ps_secondary)
861 have_secondary = true;
866 if(sbt_num_fields >= MAX_SBT_FIELDS)
870 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
872 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
873 have_secondary = true;
874 if(ps_primary == ps_secondary)
875 have_secondary = true;
876 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
878 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
882 strfree(sbt_field_title[sbt_num_fields]);
883 for(i = sbt_num_fields; i > 0; --i)
885 sbt_field_title[i] = sbt_field_title[i-1];
886 sbt_field_size[i] = sbt_field_size[i-1];
887 sbt_field[i] = sbt_field[i-1];
889 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
890 sbt_field[0] = SP_NAME;
892 LOG_INFO("fixed missing field 'name'");
896 strfree(sbt_field_title[sbt_num_fields]);
897 for(i = sbt_num_fields; i > 1; --i)
899 sbt_field_title[i] = sbt_field_title[i-1];
900 sbt_field_size[i] = sbt_field_size[i-1];
901 sbt_field[i] = sbt_field[i-1];
903 sbt_field_title[1] = strzone("|");
904 sbt_field[1] = SP_SEPARATOR;
905 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
907 LOG_INFO("fixed missing field '|'");
910 else if(!have_separator)
912 strcpy(sbt_field_title[sbt_num_fields], "|");
913 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
914 sbt_field[sbt_num_fields] = SP_SEPARATOR;
916 LOG_INFO("fixed missing field '|'");
920 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
921 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
922 sbt_field[sbt_num_fields] = ps_secondary;
924 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
928 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
929 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
930 sbt_field[sbt_num_fields] = ps_primary;
932 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
936 sbt_field[sbt_num_fields] = SP_END;
939 string Scoreboard_AddPlayerId(string pl_name, entity pl)
941 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
942 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
943 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
947 vector sbt_field_rgb;
948 string sbt_field_icon0;
949 string sbt_field_icon1;
950 string sbt_field_icon2;
951 vector sbt_field_icon0_rgb;
952 vector sbt_field_icon1_rgb;
953 vector sbt_field_icon2_rgb;
954 string Scoreboard_GetName(entity pl)
956 if(ready_waiting && pl.ready)
958 sbt_field_icon0 = "gfx/scoreboard/player_ready";
962 int f = entcs_GetClientColors(pl.sv_entnum);
964 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
965 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
966 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
967 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
968 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
971 return entcs_GetName(pl.sv_entnum);
974 string Scoreboard_GetField(entity pl, PlayerScoreField field)
976 float tmp, num, denom;
979 sbt_field_rgb = '1 1 1';
980 sbt_field_icon0 = "";
981 sbt_field_icon1 = "";
982 sbt_field_icon2 = "";
983 sbt_field_icon0_rgb = '1 1 1';
984 sbt_field_icon1_rgb = '1 1 1';
985 sbt_field_icon2_rgb = '1 1 1';
990 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
991 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
995 tmp = max(0, min(220, f-80)) / 220;
996 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1002 f = pl.ping_packetloss;
1003 tmp = pl.ping_movementloss;
1004 if(f == 0 && tmp == 0)
1006 str = ftos(ceil(f * 100));
1008 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1009 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1010 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1014 str = Scoreboard_GetName(pl);
1015 if (autocvar_hud_panel_scoreboard_playerid)
1016 str = Scoreboard_AddPlayerId(str, pl);
1020 f = pl.(scores(SP_KILLS));
1021 f -= pl.(scores(SP_SUICIDES));
1025 num = pl.(scores(SP_KILLS));
1026 denom = pl.(scores(SP_DEATHS));
1029 sbt_field_rgb = '0 1 0';
1030 str = sprintf("%d", num);
1031 } else if(num <= 0) {
1032 sbt_field_rgb = '1 0 0';
1033 str = sprintf("%.1f", num/denom);
1035 str = sprintf("%.1f", num/denom);
1039 f = pl.(scores(SP_KILLS));
1040 f -= pl.(scores(SP_DEATHS));
1043 sbt_field_rgb = '0 1 0';
1045 sbt_field_rgb = '1 1 1';
1047 sbt_field_rgb = '1 0 0';
1053 float elo = pl.(scores(SP_ELO));
1055 case -1: return "...";
1056 case -2: return _("N/A");
1057 default: return ftos(elo);
1063 float fps = pl.(scores(SP_FPS));
1066 sbt_field_rgb = '1 1 1';
1067 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1069 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1070 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1074 case SP_DMG: case SP_DMGTAKEN:
1075 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1079 float strafe_efficiency = pl.(scores(field)) / 1000;
1080 if(strafe_efficiency < -1) return "";
1081 sbt_field_rgb = '1 1 1' - (strafe_efficiency > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_efficiency);
1082 return sprintf("%.1f%%", strafe_efficiency * 100);
1085 case SP_CTS_STARTSPEED:
1086 case SP_CTS_AVGSPEED:
1087 case SP_CTS_TOPSPEED:
1089 float speed = pl.(scores(field)) * GetSpeedUnitFactor(autocvar_hud_panel_physics_speed_unit);
1090 if(speed < 0) return "";
1091 return sprintf("%d%s", speed, GetSpeedUnit(autocvar_hud_panel_physics_speed_unit));
1094 default: case SP_SCORE:
1095 tmp = pl.(scores(field));
1096 f = scores_flags(field);
1097 if(field == ps_primary)
1098 sbt_field_rgb = '1 1 0';
1099 else if(field == ps_secondary)
1100 sbt_field_rgb = '0 1 1';
1102 sbt_field_rgb = '1 1 1';
1103 return ScoreString(f, tmp);
1108 float sbt_fixcolumnwidth_len;
1109 float sbt_fixcolumnwidth_iconlen;
1110 float sbt_fixcolumnwidth_marginlen;
1112 string Scoreboard_FixColumnWidth(int i, string str)
1118 sbt_fixcolumnwidth_iconlen = 0;
1120 if(sbt_field_icon0 != "")
1122 sz = draw_getimagesize(sbt_field_icon0);
1124 if(sbt_fixcolumnwidth_iconlen < f)
1125 sbt_fixcolumnwidth_iconlen = f;
1128 if(sbt_field_icon1 != "")
1130 sz = draw_getimagesize(sbt_field_icon1);
1132 if(sbt_fixcolumnwidth_iconlen < f)
1133 sbt_fixcolumnwidth_iconlen = f;
1136 if(sbt_field_icon2 != "")
1138 sz = draw_getimagesize(sbt_field_icon2);
1140 if(sbt_fixcolumnwidth_iconlen < f)
1141 sbt_fixcolumnwidth_iconlen = f;
1144 if(sbt_fixcolumnwidth_iconlen != 0)
1146 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1147 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1150 sbt_fixcolumnwidth_marginlen = 0;
1152 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1155 float remaining_space = 0;
1156 for(j = 0; j < sbt_num_fields; ++j)
1158 if (sbt_field[i] != SP_SEPARATOR)
1159 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1160 sbt_field_size[i] = panel_size.x - remaining_space;
1162 if (sbt_fixcolumnwidth_iconlen != 0)
1163 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1164 float namesize = panel_size.x - remaining_space;
1165 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1166 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1168 max_namesize = vid_conwidth - remaining_space;
1171 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1173 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1174 if(sbt_field_size[i] < f)
1175 sbt_field_size[i] = f;
1180 void Scoreboard_initFieldSizes()
1182 for(int i = 0; i < sbt_num_fields; ++i)
1184 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1185 Scoreboard_FixColumnWidth(i, "");
1189 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1192 vector column_dim = eY * panel_size.y;
1194 column_dim.y -= 1.25 * hud_fontsize.y;
1195 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1196 pos.x += hud_fontsize.x * 0.5;
1197 for(i = 0; i < sbt_num_fields; ++i)
1199 if(sbt_field[i] == SP_SEPARATOR)
1201 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1204 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1205 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1206 pos.x += column_dim.x;
1208 if(sbt_field[i] == SP_SEPARATOR)
1210 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1211 for(i = sbt_num_fields - 1; i > 0; --i)
1213 if(sbt_field[i] == SP_SEPARATOR)
1216 pos.x -= sbt_field_size[i];
1221 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1222 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1225 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1226 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1227 pos.x -= hud_fontsize.x;
1231 pos.x = panel_pos.x;
1232 pos.y += 1.25 * hud_fontsize.y;
1236 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1238 TC(bool, is_self); TC(int, pl_number);
1240 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1242 vector h_pos = item_pos;
1243 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1244 // alternated rows highlighting
1245 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1247 if (pl == scoreboard_selected_player)
1248 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1251 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1252 else if((sbt_highlight) && (!(pl_number % 2)))
1253 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1255 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1257 vector pos = item_pos;
1258 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1260 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1262 pos.x += hud_fontsize.x * 0.5;
1263 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1264 vector tmp = '0 0 0';
1266 PlayerScoreField field;
1267 for(i = 0; i < sbt_num_fields; ++i)
1269 field = sbt_field[i];
1270 if(field == SP_SEPARATOR)
1273 if(is_spec && field != SP_NAME && field != SP_PING) {
1274 pos.x += sbt_field_size[i] + hud_fontsize.x;
1277 str = Scoreboard_GetField(pl, field);
1278 str = Scoreboard_FixColumnWidth(i, str);
1280 pos.x += sbt_field_size[i] + hud_fontsize.x;
1282 if(field == SP_NAME) {
1283 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1284 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1286 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1287 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1290 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1291 if(sbt_field_icon0 != "")
1292 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1293 if(sbt_field_icon1 != "")
1294 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1295 if(sbt_field_icon2 != "")
1296 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1299 if(sbt_field[i] == SP_SEPARATOR)
1301 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1302 for(i = sbt_num_fields-1; i > 0; --i)
1304 field = sbt_field[i];
1305 if(field == SP_SEPARATOR)
1308 if(is_spec && field != SP_NAME && field != SP_PING) {
1309 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1313 str = Scoreboard_GetField(pl, field);
1314 str = Scoreboard_FixColumnWidth(i, str);
1316 if(field == SP_NAME) {
1317 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1318 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1320 tmp.x = sbt_fixcolumnwidth_len;
1321 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1324 tmp.x = sbt_field_size[i];
1325 if(sbt_field_icon0 != "")
1326 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1327 if(sbt_field_icon1 != "")
1328 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1329 if(sbt_field_icon2 != "")
1330 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1331 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1336 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1339 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1342 vector h_pos = item_pos;
1343 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1345 bool complete = (this_team == NUM_SPECTATOR);
1348 if((sbt_highlight) && (!(pl_number % 2)))
1349 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1351 vector pos = item_pos;
1352 pos.x += hud_fontsize.x * 0.5;
1353 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1355 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1357 width_limit -= stringwidth("...", false, hud_fontsize);
1358 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1359 static float max_name_width = 0;
1361 float fieldsize = 0;
1362 float min_fieldsize = 0;
1363 float fieldpadding = hud_fontsize.x * 0.25;
1364 if(this_team == NUM_SPECTATOR)
1366 if(autocvar_hud_panel_scoreboard_spectators_showping)
1367 min_fieldsize = stringwidth("999", false, hud_fontsize);
1369 else if(autocvar_hud_panel_scoreboard_others_showscore)
1370 min_fieldsize = stringwidth("99", false, hud_fontsize);
1371 for(i = 0; pl; pl = pl.sort_next)
1373 if(pl.team != this_team)
1375 if(pl == ignored_pl)
1379 if(this_team == NUM_SPECTATOR)
1381 if(autocvar_hud_panel_scoreboard_spectators_showping)
1382 field = Scoreboard_GetField(pl, SP_PING);
1384 else if(autocvar_hud_panel_scoreboard_others_showscore)
1385 field = Scoreboard_GetField(pl, SP_SCORE);
1387 string str = entcs_GetName(pl.sv_entnum);
1388 if (autocvar_hud_panel_scoreboard_playerid)
1389 str = Scoreboard_AddPlayerId(str, pl);
1390 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1391 float column_width = stringwidth(str, true, hud_fontsize);
1392 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1394 if(column_width > max_name_width)
1395 max_name_width = column_width;
1396 column_width = max_name_width;
1400 fieldsize = stringwidth(field, false, hud_fontsize);
1401 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1404 if(pos.x + column_width > width_limit)
1409 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1414 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1415 pos.y += hud_fontsize.y * 1.25;
1419 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1421 if (pl == scoreboard_selected_player)
1423 h_size.x = column_width + hud_fontsize.x * 0.25;
1424 h_size.y = hud_fontsize.y;
1425 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1429 vector name_pos = pos;
1430 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1431 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1432 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1435 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1436 h_size.y = hud_fontsize.y;
1437 vector field_pos = pos;
1438 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1439 field_pos.x += column_width - h_size.x;
1441 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1442 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1443 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1447 h_size.x = column_width + hud_fontsize.x * 0.25;
1448 h_size.y = hud_fontsize.y;
1449 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1451 pos.x += column_width;
1452 pos.x += hud_fontsize.x;
1454 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1457 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1459 int max_players = 999;
1460 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1462 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1465 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1466 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1467 height /= team_count;
1470 height -= panel_bg_padding * 2; // - padding
1471 max_players = floor(height / (hud_fontsize.y * 1.25));
1472 if(max_players <= 1)
1474 if(max_players == tm.team_size)
1479 entity me = playerslots[current_player];
1481 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1482 panel_size.y += panel_bg_padding * 2;
1484 vector scoreboard_selected_hl_pos = pos;
1485 vector scoreboard_selected_hl_size = '0 0 0';
1486 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1487 scoreboard_selected_hl_size.y = panel_size.y;
1491 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1492 if(panel.current_panel_bg != "0")
1493 end_pos.y += panel_bg_border * 2;
1495 if(panel_bg_padding)
1497 panel_pos += '1 1 0' * panel_bg_padding;
1498 panel_size -= '2 2 0' * panel_bg_padding;
1502 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1506 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1508 pos.y += 1.25 * hud_fontsize.y;
1511 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1513 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1516 // print header row and highlight columns
1517 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1519 // fill the table and draw the rows
1520 bool is_self = false;
1521 bool self_shown = false;
1523 for(pl = players.sort_next; pl; pl = pl.sort_next)
1525 if(pl.team != tm.team)
1527 if(i == max_players - 2 && pl != me)
1529 if(!self_shown && me.team == tm.team)
1531 Scoreboard_DrawItem(pos, rgb, me, true, i);
1533 pos.y += 1.25 * hud_fontsize.y;
1537 if(i >= max_players - 1)
1539 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1542 is_self = (pl.sv_entnum == current_player);
1543 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1546 pos.y += 1.25 * hud_fontsize.y;
1550 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1552 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1554 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1555 _alpha *= panel_fg_alpha;
1557 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1561 panel_size.x += panel_bg_padding * 2; // restore initial width
1565 bool Scoreboard_WouldDraw()
1567 if (scoreboard_ui_enabled)
1569 if (scoreboard_ui_disabling)
1571 if (scoreboard_fade_alpha == 0)
1572 HUD_Scoreboard_UI_Disable_Instantly();
1575 if (intermission && scoreboard_ui_enabled == 2)
1577 HUD_Scoreboard_UI_Disable_Instantly();
1582 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1584 else if (QuickMenu_IsOpened())
1586 else if (HUD_Radar_Clickable())
1588 else if (sb_showscores) // set by +showscores engine command
1590 else if (intermission == 1)
1592 else if (intermission == 2)
1594 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1595 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1599 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1604 float average_accuracy;
1605 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1607 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1609 WepSet weapons_stat = WepSet_GetFromStat();
1610 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1611 int disownedcnt = 0;
1613 FOREACH(Weapons, it != WEP_Null, {
1614 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1616 WepSet set = it.m_wepset;
1617 if(it.spawnflags & WEP_TYPE_OTHER)
1622 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1624 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1631 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1632 if (weapon_cnt <= 0) return pos;
1635 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1637 int columns = ceil(weapon_cnt / rows);
1639 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1640 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1641 float height = weapon_height + hud_fontsize.y;
1643 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);
1644 pos.y += 1.25 * hud_fontsize.y;
1645 if(panel.current_panel_bg != "0")
1646 pos.y += panel_bg_border;
1649 panel_size.y = height * rows;
1650 panel_size.y += panel_bg_padding * 2;
1652 float panel_bg_alpha_save = panel_bg_alpha;
1653 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1655 panel_bg_alpha = panel_bg_alpha_save;
1657 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1658 if(panel.current_panel_bg != "0")
1659 end_pos.y += panel_bg_border * 2;
1661 if(panel_bg_padding)
1663 panel_pos += '1 1 0' * panel_bg_padding;
1664 panel_size -= '2 2 0' * panel_bg_padding;
1668 vector tmp = panel_size;
1670 float weapon_width = tmp.x / columns / rows;
1673 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1677 // column highlighting
1678 for (int i = 0; i < columns; ++i)
1680 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);
1683 for (int i = 0; i < rows; ++i)
1684 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1687 average_accuracy = 0;
1688 int weapons_with_stats = 0;
1690 pos.x += weapon_width / 2;
1692 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1695 Accuracy_LoadColors();
1697 float oldposx = pos.x;
1701 FOREACH(Weapons, it != WEP_Null, {
1702 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1704 WepSet set = it.m_wepset;
1705 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1707 if (it.spawnflags & WEP_TYPE_OTHER)
1711 if (weapon_stats >= 0)
1712 weapon_alpha = sbt_fg_alpha;
1714 weapon_alpha = 0.2 * sbt_fg_alpha;
1717 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1719 if (weapon_stats >= 0) {
1720 weapons_with_stats += 1;
1721 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1723 string s = sprintf("%d%%", weapon_stats * 100);
1724 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1726 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1727 rgb = Accuracy_GetColor(weapon_stats);
1729 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1731 tmpos.x += weapon_width * rows;
1732 pos.x += weapon_width * rows;
1733 if (rows == 2 && column == columns - 1) {
1741 if (weapons_with_stats)
1742 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1744 panel_size.x += panel_bg_padding * 2; // restore initial width
1749 bool is_item_filtered(entity it)
1751 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1753 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1756 if (it.instanceOfArmor || it.instanceOfHealth)
1758 int ha_mask = floor(mask) % 10;
1761 default: return false;
1762 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1763 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1764 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1765 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1768 if (it.instanceOfAmmo)
1770 int ammo_mask = floor(mask / 10) % 10;
1771 return (ammo_mask == 1);
1776 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1778 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1780 int disowned_cnt = 0;
1781 int uninteresting_cnt = 0;
1782 IL_EACH(default_order_items, true, {
1783 int q = g_inventory.inv_items[it.m_id];
1784 //q = 1; // debug: display all items
1785 if (is_item_filtered(it))
1786 ++uninteresting_cnt;
1790 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1791 int n = items_cnt - disowned_cnt;
1792 if (n <= 0) return pos;
1794 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1795 int columns = max(6, ceil(n / rows));
1797 float item_height = hud_fontsize.y * 2.3;
1798 float height = item_height + hud_fontsize.y;
1800 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1801 pos.y += 1.25 * hud_fontsize.y;
1802 if(panel.current_panel_bg != "0")
1803 pos.y += panel_bg_border;
1806 panel_size.y = height * rows;
1807 panel_size.y += panel_bg_padding * 2;
1809 float panel_bg_alpha_save = panel_bg_alpha;
1810 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1812 panel_bg_alpha = panel_bg_alpha_save;
1814 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1815 if(panel.current_panel_bg != "0")
1816 end_pos.y += panel_bg_border * 2;
1818 if(panel_bg_padding)
1820 panel_pos += '1 1 0' * panel_bg_padding;
1821 panel_size -= '2 2 0' * panel_bg_padding;
1825 vector tmp = panel_size;
1827 float item_width = tmp.x / columns / rows;
1830 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1834 // column highlighting
1835 for (int i = 0; i < columns; ++i)
1837 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);
1840 for (int i = 0; i < rows; ++i)
1841 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1845 pos.x += item_width / 2;
1847 float oldposx = pos.x;
1851 IL_EACH(default_order_items, !is_item_filtered(it), {
1852 int n = g_inventory.inv_items[it.m_id];
1853 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1854 if (n <= 0) continue;
1855 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);
1857 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1858 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1859 tmpos.x += item_width * rows;
1860 pos.x += item_width * rows;
1861 if (rows == 2 && column == columns - 1) {
1869 panel_size.x += panel_bg_padding * 2; // restore initial width
1874 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1876 pos.x += hud_fontsize.x * 0.25;
1877 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1878 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1879 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1881 pos.y += hud_fontsize.y;
1886 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1887 float stat_secrets_found, stat_secrets_total;
1888 float stat_monsters_killed, stat_monsters_total;
1892 // get monster stats
1893 stat_monsters_killed = STAT(MONSTERS_KILLED);
1894 stat_monsters_total = STAT(MONSTERS_TOTAL);
1896 // get secrets stats
1897 stat_secrets_found = STAT(SECRETS_FOUND);
1898 stat_secrets_total = STAT(SECRETS_TOTAL);
1900 // get number of rows
1901 if(stat_secrets_total)
1903 if(stat_monsters_total)
1906 // if no rows, return
1910 // draw table header
1911 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1912 pos.y += 1.25 * hud_fontsize.y;
1913 if(panel.current_panel_bg != "0")
1914 pos.y += panel_bg_border;
1917 panel_size.y = hud_fontsize.y * rows;
1918 panel_size.y += panel_bg_padding * 2;
1921 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1922 if(panel.current_panel_bg != "0")
1923 end_pos.y += panel_bg_border * 2;
1925 if(panel_bg_padding)
1927 panel_pos += '1 1 0' * panel_bg_padding;
1928 panel_size -= '2 2 0' * panel_bg_padding;
1932 vector tmp = panel_size;
1935 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1938 if(stat_monsters_total)
1940 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1941 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1945 if(stat_secrets_total)
1947 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1948 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1951 panel_size.x += panel_bg_padding * 2; // restore initial width
1955 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1958 RANKINGS_RECEIVED_CNT = 0;
1959 for (i=RANKINGS_CNT-1; i>=0; --i)
1961 ++RANKINGS_RECEIVED_CNT;
1963 if (RANKINGS_RECEIVED_CNT == 0)
1966 vector hl_rgb = rgb + '0.5 0.5 0.5';
1968 vector scoreboard_selected_hl_pos = pos;
1970 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1971 pos.y += 1.25 * hud_fontsize.y;
1972 if(panel.current_panel_bg != "0")
1973 pos.y += panel_bg_border;
1975 vector scoreboard_selected_hl_size = '0 0 0';
1976 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1977 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1982 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1984 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1989 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1991 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1995 float ranksize = 3 * hud_fontsize.x;
1996 float timesize = 5 * hud_fontsize.x;
1997 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1998 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1999 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2002 rankings_cnt = RANKINGS_RECEIVED_CNT;
2003 rankings_rows = ceil(rankings_cnt / rankings_columns);
2006 // expand name column to fill the entire row
2007 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2008 namesize += available_space;
2009 columnsize.x += available_space;
2011 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2012 panel_size.y += panel_bg_padding * 2;
2013 scoreboard_selected_hl_size.y += panel_size.y;
2017 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2018 if(panel.current_panel_bg != "0")
2019 end_pos.y += panel_bg_border * 2;
2021 if(panel_bg_padding)
2023 panel_pos += '1 1 0' * panel_bg_padding;
2024 panel_size -= '2 2 0' * panel_bg_padding;
2030 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2032 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2034 int column = 0, j = 0;
2035 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2036 int start_item = rankings_start_column * rankings_rows;
2037 for(i = start_item; i < start_item + rankings_cnt; ++i)
2039 int t = grecordtime[i];
2043 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2044 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2045 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2046 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2048 str = count_ordinal(i+1);
2049 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2050 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2051 str = ColorTranslateRGB(grecordholder[i]);
2053 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2054 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2056 pos.y += 1.25 * hud_fontsize.y;
2058 if(j >= rankings_rows)
2062 pos.x += panel_size.x / rankings_columns;
2063 pos.y = panel_pos.y;
2066 strfree(zoned_name_self);
2068 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2070 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2071 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2074 panel_size.x += panel_bg_padding * 2; // restore initial width
2078 bool have_weapon_stats;
2079 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2081 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2083 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2086 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2087 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2093 if (!have_weapon_stats)
2095 FOREACH(Weapons, it != WEP_Null, {
2096 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2097 if (weapon_stats >= 0)
2099 have_weapon_stats = true;
2103 if (!have_weapon_stats)
2110 bool have_item_stats;
2111 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2113 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2115 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2118 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2119 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2125 if (!have_item_stats)
2127 IL_EACH(default_order_items, true, {
2128 if (!is_item_filtered(it))
2130 int q = g_inventory.inv_items[it.m_id];
2131 //q = 1; // debug: display all items
2134 have_item_stats = true;
2139 if (!have_item_stats)
2146 vector Scoreboard_Spectators_Draw(vector pos) {
2151 for(pl = players.sort_next; pl; pl = pl.sort_next)
2153 if(pl.team == NUM_SPECTATOR)
2155 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2156 if(tm.team == NUM_SPECTATOR)
2158 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2159 draw_beginBoldFont();
2160 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2162 pos.y += 1.25 * hud_fontsize.y;
2164 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2165 pos.y += 1.25 * hud_fontsize.y;
2170 if (str != "") // if there's at least one spectator
2171 pos.y += 0.5 * hud_fontsize.y;
2176 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2178 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2179 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2180 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit),
2181 (s_label == "score") ? CTX(_("SCO^points")) :
2182 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2185 void Scoreboard_Draw()
2187 if(!autocvar__hud_configure)
2189 if(!hud_draw_maximized) return;
2191 // frametime checks allow to toggle the scoreboard even when the game is paused
2192 if(scoreboard_active) {
2193 if (scoreboard_fade_alpha == 0)
2194 scoreboard_time = time;
2195 if(hud_configure_menu_open == 1)
2196 scoreboard_fade_alpha = 1;
2197 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2198 if (scoreboard_fadeinspeed && frametime)
2199 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2201 scoreboard_fade_alpha = 1;
2202 if(hud_fontsize_str != autocvar_hud_fontsize)
2204 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2205 Scoreboard_initFieldSizes();
2206 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2210 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2211 if (scoreboard_fadeoutspeed && frametime)
2212 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2214 scoreboard_fade_alpha = 0;
2217 if (!scoreboard_fade_alpha)
2219 scoreboard_acc_fade_alpha = 0;
2220 scoreboard_itemstats_fade_alpha = 0;
2225 scoreboard_fade_alpha = 0;
2227 if (autocvar_hud_panel_scoreboard_dynamichud)
2230 HUD_Scale_Disable();
2232 if(scoreboard_fade_alpha <= 0)
2234 panel_fade_alpha *= scoreboard_fade_alpha;
2235 HUD_Panel_LoadCvars();
2237 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2238 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2239 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2240 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2241 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2242 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2243 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2245 // don't overlap with con_notify
2246 if(!autocvar__hud_configure)
2247 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2249 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2250 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2251 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2252 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2253 panel_pos.x = scoreboard_left;
2254 panel_size.x = fixed_scoreboard_width;
2256 Scoreboard_UpdatePlayerTeams();
2258 scoreboard_top = panel_pos.y;
2259 vector pos = panel_pos;
2264 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2266 // Begin of Game Info Section
2267 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2268 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2270 // Game Info: Game Type
2271 if (scoreboard_ui_enabled == 2)
2272 str = _("Team Selection");
2274 str = MapInfo_Type_ToText(gametype);
2275 draw_beginBoldFont();
2276 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);
2279 pos.y += sb_gameinfo_type_fontsize.y;
2280 // Game Info: Game Detail
2281 if (scoreboard_ui_enabled == 2)
2283 if (scoreboard_selected_team)
2284 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2286 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2287 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);
2289 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2290 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2291 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);
2295 float tl = STAT(TIMELIMIT);
2296 float fl = STAT(FRAGLIMIT);
2297 float ll = STAT(LEADLIMIT);
2298 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2301 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2302 if(!gametype.m_hidelimits)
2307 str = strcat(str, "^7 / "); // delimiter
2308 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2312 if(tl > 0 || fl > 0)
2315 if (ll_and_fl && fl > 0)
2316 str = strcat(str, "^7 & ");
2318 str = strcat(str, "^7 / ");
2320 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2323 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
2324 // map name and player count
2328 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2329 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2330 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2332 // End of Game Info Section
2334 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2335 if(panel.current_panel_bg != "0")
2336 pos.y += panel_bg_border;
2338 // Draw the scoreboard
2339 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2342 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2346 vector panel_bg_color_save = panel_bg_color;
2347 vector team_score_baseoffset;
2348 vector team_size_baseoffset;
2349 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2351 // put team score to the left of scoreboard (and team size to the right)
2352 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2353 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2354 if(panel.current_panel_bg != "0")
2356 team_score_baseoffset.x -= panel_bg_border;
2357 team_size_baseoffset.x += panel_bg_border;
2362 // put team score to the right of scoreboard (and team size to the left)
2363 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2364 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2365 if(panel.current_panel_bg != "0")
2367 team_score_baseoffset.x += panel_bg_border;
2368 team_size_baseoffset.x -= panel_bg_border;
2372 int team_size_total = 0;
2373 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2375 // calculate team size total (sum of all team sizes)
2376 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2377 if(tm.team != NUM_SPECTATOR)
2378 team_size_total += tm.team_size;
2381 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2383 if(tm.team == NUM_SPECTATOR)
2388 draw_beginBoldFont();
2389 vector rgb = Team_ColorRGB(tm.team);
2390 str = ftos(tm.(teamscores(ts_primary)));
2391 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2393 // team score on the left (default)
2394 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2398 // team score on the right
2399 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2401 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2403 // team size (if set to show on the side)
2404 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2406 // calculate the starting position for the whole team size info string
2407 str = sprintf("%d/%d", tm.team_size, team_size_total);
2408 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2410 // team size on the left
2411 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2415 // team size on the right
2416 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2418 str = sprintf("%d", tm.team_size);
2419 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2420 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2421 str = sprintf("/%d", team_size_total);
2422 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2426 // secondary score, e.g. keyhunt
2427 if(ts_primary != ts_secondary)
2429 str = ftos(tm.(teamscores(ts_secondary)));
2430 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2433 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2438 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2441 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2444 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2445 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2446 else if(panel_bg_color_team > 0)
2447 panel_bg_color = rgb * panel_bg_color_team;
2449 panel_bg_color = rgb;
2450 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2452 panel_bg_color = panel_bg_color_save;
2456 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2457 if(tm.team != NUM_SPECTATOR)
2460 // display it anyway
2461 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2464 // draw scoreboard spectators before accuracy and item stats
2465 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2466 pos = Scoreboard_Spectators_Draw(pos);
2469 // draw accuracy and item stats
2470 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2471 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2472 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2473 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2475 // draw scoreboard spectators after accuracy and item stats and before rankings
2476 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2477 pos = Scoreboard_Spectators_Draw(pos);
2480 if(MUTATOR_CALLHOOK(ShowRankings)) {
2481 string ranktitle = M_ARGV(0, string);
2482 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2483 if(race_speedaward_alltimebest)
2486 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2490 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2491 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2492 str = strcat(str, " / ");
2494 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2495 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2496 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2497 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2499 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2504 // draw scoreboard spectators after rankings
2505 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2506 pos = Scoreboard_Spectators_Draw(pos);
2509 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2511 // draw scoreboard spectators after mapstats
2512 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2513 pos = Scoreboard_Spectators_Draw(pos);
2517 // print information about respawn status
2518 float respawn_time = STAT(RESPAWN_TIME);
2519 if(!intermission && respawn_time)
2521 if(respawn_time < 0)
2523 // a negative number means we are awaiting respawn, time value is still the same
2524 respawn_time *= -1; // remove mark now that we checked it
2526 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2527 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2529 str = sprintf(_("^1Respawning in ^3%s^1..."),
2530 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2531 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2533 count_seconds(ceil(respawn_time - time))
2537 else if(time < respawn_time)
2539 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2540 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2541 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2543 count_seconds(ceil(respawn_time - time))
2547 else if(time >= respawn_time)
2548 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2550 pos.y += 1.2 * hud_fontsize.y;
2551 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2554 pos.y += hud_fontsize.y;
2555 if (scoreboard_fade_alpha < 1)
2556 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2557 else if (pos.y != scoreboard_bottom)
2559 if (pos.y > scoreboard_bottom)
2560 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2562 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2567 if (scoreboard_fade_alpha == 1)
2569 if (scoreboard_bottom > 0.95 * vid_conheight)
2570 rankings_rows = max(1, rankings_rows - 1);
2571 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2572 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2574 rankings_cnt = rankings_rows * rankings_columns;