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 "avgspeed": if (!mode) return CTX(_("SCO^average speed"));else LOG_HELP(strcat("^3", "avgspeed", " ^7", _("Average speed (CTS)")));
166 case "topspeed": if (!mode) return CTX(_("SCO^top speed")); else LOG_HELP(strcat("^3", "topspeed", " ^7", _("Top speed (CTS)")));
167 case "startspeed": if (!mode) return CTX(_("SCO^start speed")); else LOG_HELP(strcat("^3", "startspeed", " ^7", _("Start speed (CTS)")));
168 case "strafe": if (!mode) return CTX(_("SCO^strafe")); else LOG_HELP(strcat("^3", "strafe", " ^7", _("Strafe efficiency (CTS)")));
169 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
170 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
171 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
172 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
173 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
174 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
175 default: return label;
180 bool scoreboard_ui_disabling;
181 void HUD_Scoreboard_UI_Disable()
183 scoreboard_ui_disabling = true;
184 sb_showscores = false;
187 void HUD_Scoreboard_UI_Disable_Instantly()
189 scoreboard_ui_disabling = false;
190 scoreboard_ui_enabled = 0;
191 scoreboard_selected_panel = 0;
192 scoreboard_selected_player = NULL;
193 scoreboard_selected_team = NULL;
196 // mode: 0 normal, 1 team selection
197 void Scoreboard_UI_Enable(int mode)
203 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
206 // release player's pressed keys as they aren't released elsewhere
207 // in particular jump needs to be released as it may open the team selection
208 // (when server detects jump has been pressed it sends the command to open the team selection)
209 Release_Common_Keys();
210 scoreboard_ui_enabled = 2;
211 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
215 if (scoreboard_ui_enabled == 1)
217 scoreboard_ui_enabled = 1;
218 scoreboard_selected_panel = SB_PANEL_FIRST;
220 scoreboard_selected_player = NULL;
221 scoreboard_selected_team = NULL;
222 scoreboard_selected_panel_time = time;
225 int rankings_start_column;
226 int rankings_rows = 0;
227 int rankings_columns = 0;
228 int rankings_cnt = 0;
229 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
233 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
238 mousepos.x = nPrimary;
239 mousepos.y = nSecondary;
246 // at this point bInputType can only be 0 or 1 (key pressed or released)
247 bool key_pressed = (bInputType == 0);
249 // ESC to exit (TAB-ESC works too)
250 if(nPrimary == K_ESCAPE)
254 HUD_Scoreboard_UI_Disable();
258 // block any input while a menu dialog is fading
259 if(autocvar__menu_alpha)
265 // allow console bind to work
266 string con_keys = findkeysforcommand("toggleconsole", 0);
267 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
269 bool hit_con_bind = false;
271 for (i = 0; i < keys; ++i)
273 if(nPrimary == stof(argv(i)))
278 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
279 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
280 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
281 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
284 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
285 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
286 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
287 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
290 if(nPrimary == K_TAB)
294 if (scoreboard_ui_enabled == 2)
296 if (hudShiftState & S_SHIFT)
299 goto downarrow_action;
302 if (hudShiftState & S_SHIFT)
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_FIRST)
308 scoreboard_selected_panel = SB_PANEL_MAX;
312 ++scoreboard_selected_panel;
313 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
314 ++scoreboard_selected_panel;
315 if (scoreboard_selected_panel > SB_PANEL_MAX)
316 scoreboard_selected_panel = SB_PANEL_FIRST;
319 scoreboard_selected_panel_time = time;
321 else if(nPrimary == K_DOWNARROW)
325 LABEL(downarrow_action);
326 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
328 if (scoreboard_ui_enabled == 2)
330 entity curr_team = NULL;
331 bool scoreboard_selected_team_found = false;
332 if (!scoreboard_selected_team)
333 scoreboard_selected_team_found = true;
335 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
337 if(tm.team == NUM_SPECTATOR)
340 if (scoreboard_selected_team_found)
342 if (scoreboard_selected_team == tm)
343 scoreboard_selected_team_found = true;
346 if (curr_team == scoreboard_selected_team) // loop reached the last team
348 scoreboard_selected_team = curr_team;
353 entity curr_pl = NULL;
354 bool scoreboard_selected_player_found = false;
355 if (!scoreboard_selected_player)
356 scoreboard_selected_player_found = true;
358 for(tm = teams.sort_next; tm; tm = tm.sort_next)
360 if(tm.team != NUM_SPECTATOR)
361 for(pl = players.sort_next; pl; pl = pl.sort_next)
363 if(pl.team != tm.team)
366 if (scoreboard_selected_player_found)
368 if (scoreboard_selected_player == pl)
369 scoreboard_selected_player_found = true;
373 if (curr_pl == scoreboard_selected_player) // loop reached the last player
375 scoreboard_selected_player = curr_pl;
379 else if(nPrimary == K_UPARROW)
383 LABEL(uparrow_action);
384 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
386 if (scoreboard_ui_enabled == 2)
388 entity prev_team = NULL;
389 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
391 if(tm.team == NUM_SPECTATOR)
393 if (tm == scoreboard_selected_team)
398 scoreboard_selected_team = prev_team;
402 entity prev_pl = NULL;
404 for(tm = teams.sort_next; tm; tm = tm.sort_next)
406 if(tm.team != NUM_SPECTATOR)
407 for(pl = players.sort_next; pl; pl = pl.sort_next)
409 if(pl.team != tm.team)
411 if (pl == scoreboard_selected_player)
417 scoreboard_selected_player = prev_pl;
421 else if(nPrimary == K_RIGHTARROW)
425 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
426 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
428 else if(nPrimary == K_LEFTARROW)
432 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
433 rankings_start_column = max(rankings_start_column - 1, 0);
435 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
439 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
441 if (scoreboard_ui_enabled == 2)
444 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
447 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
448 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
449 HUD_Scoreboard_UI_Disable();
451 else if (scoreboard_selected_player)
452 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
455 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
459 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
461 switch (scoreboard_selected_columns_layout)
464 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
466 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
467 scoreboard_selected_columns_layout = 1;
472 localcmd(sprintf("scoreboard_columns_set default\n"));
473 scoreboard_selected_columns_layout = 2;
476 localcmd(sprintf("scoreboard_columns_set all\n"));
477 scoreboard_selected_columns_layout = 0;
482 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
486 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
487 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
489 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
493 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
495 if (scoreboard_selected_player)
497 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
498 HUD_Scoreboard_UI_Disable();
502 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
506 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
508 if (scoreboard_selected_player)
509 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
512 else if(hit_con_bind || nPrimary == K_PAUSE)
518 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
519 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
521 void Scoreboard_InitScores()
525 ps_primary = ps_secondary = NULL;
526 ts_primary = ts_secondary = -1;
527 FOREACH(Scores, true, {
528 if(scores_flags(it) & SFL_NOT_SORTABLE)
530 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
531 if(f == SFL_SORT_PRIO_PRIMARY)
533 if(f == SFL_SORT_PRIO_SECONDARY)
536 if(ps_secondary == NULL)
537 ps_secondary = ps_primary;
539 for(i = 0; i < MAX_TEAMSCORE; ++i)
541 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
542 if(f == SFL_SORT_PRIO_PRIMARY)
544 if(f == SFL_SORT_PRIO_SECONDARY)
547 if(ts_secondary == -1)
548 ts_secondary = ts_primary;
550 Cmd_Scoreboard_SetFields(0);
554 void Scoreboard_UpdatePlayerTeams()
556 static float update_time;
557 if (time <= update_time)
564 for(pl = players.sort_next; pl; pl = pl.sort_next)
566 numplayers += pl.team != NUM_SPECTATOR;
568 int Team = entcs_GetScoreTeam(pl.sv_entnum);
569 if(SetTeam(pl, Team))
572 Scoreboard_UpdatePlayerPos(pl);
576 pl = players.sort_next;
581 print(strcat("PNUM: ", ftos(num), "\n"));
586 int Scoreboard_CompareScore(int vl, int vr, int f)
588 TC(int, vl); TC(int, vr); TC(int, f);
589 if(f & SFL_ZERO_IS_WORST)
591 if(vl == 0 && vr != 0)
593 if(vl != 0 && vr == 0)
597 return IS_INCREASING(f);
599 return IS_DECREASING(f);
603 float Scoreboard_ComparePlayerScores(entity left, entity right)
605 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
606 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
613 if(vl == NUM_SPECTATOR)
615 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
617 if(!left.gotscores && right.gotscores)
622 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
623 if (res >= 0) return res;
625 if (ps_secondary && ps_secondary != ps_primary)
627 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
628 if (res >= 0) return res;
631 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
632 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
633 if (res >= 0) return res;
636 if (left.sv_entnum < right.sv_entnum)
642 void Scoreboard_UpdatePlayerPos(entity player)
645 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
647 SORT_SWAP(player, ent);
649 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
651 SORT_SWAP(ent, player);
655 float Scoreboard_CompareTeamScores(entity left, entity right)
657 if(left.team == NUM_SPECTATOR)
659 if(right.team == NUM_SPECTATOR)
664 for(int i = -2; i < MAX_TEAMSCORE; ++i)
668 if (fld_idx == -1) fld_idx = ts_primary;
669 else if (ts_secondary == ts_primary) continue;
670 else fld_idx = ts_secondary;
675 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
678 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
679 if (r >= 0) return r;
682 if (left.team < right.team)
688 void Scoreboard_UpdateTeamPos(entity Team)
691 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
693 SORT_SWAP(Team, ent);
695 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
697 SORT_SWAP(ent, Team);
701 void Cmd_Scoreboard_Help()
703 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
704 LOG_HELP(_("Usage:"));
705 LOG_HELP("^2scoreboard_columns_set ^3default");
706 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
707 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
708 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
709 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
710 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
711 LOG_HELP(_("The following field names are recognized (case insensitive):"));
717 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
718 "of game types, then a slash, to make the field show up only in these\n"
719 "or in all but these game types. You can also specify 'all' as a\n"
720 "field to show all fields available for the current game mode."));
723 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
724 "include/exclude ALL teams/noteams game modes."));
727 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
728 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
729 "right of the vertical bar aligned to the right."));
730 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
731 "other gamemodes except DM."));
734 // NOTE: adding a gametype with ? to not warn for an optional field
735 // make sure it's excluded in a previous exclusive rule, if any
736 // otherwise the previous exclusive rule warns anyway
737 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
738 #define SCOREBOARD_DEFAULT_COLUMNS \
739 "ping pl fps name |" \
740 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
741 " -teams,lms/deaths +ft,tdm/deaths" \
743 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
744 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
745 " +tdm,ft,dom,ons,as/teamkills"\
746 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
747 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
748 " +lms/lives +lms/rank" \
749 " +kh/kckills +kh/losses +kh/caps" \
750 " ?+rc/laps ?+rc/time ?+cts/strafe ?+cts/startspeed ?+cts/avgspeed ?+cts/topspeed +rc,cts/fastest" \
751 " +as/objectives +nb/faults +nb/goals" \
752 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
753 " +dom/ticks +dom/takes" \
754 " -lms,rc,cts,inv,nb/score"
756 void Cmd_Scoreboard_SetFields(int argc)
761 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
765 return; // do nothing, we don't know gametype and scores yet
767 // sbt_fields uses strunzone on the titles!
768 if(!sbt_field_title[0])
769 for(i = 0; i < MAX_SBT_FIELDS; ++i)
770 sbt_field_title[i] = strzone("(null)");
772 // TODO: re enable with gametype dependant cvars?
773 if(argc < 3) // no arguments provided
774 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
777 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
781 if(argv(2) == "default" || argv(2) == "expand_default")
783 if(argv(2) == "expand_default")
784 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
785 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
787 else if(argv(2) == "all" || argv(2) == "ALL")
789 string s = "ping pl name |"; // scores without label (not really scores)
792 // scores without label
793 s = strcat(s, " ", "sum");
794 s = strcat(s, " ", "kdratio");
795 s = strcat(s, " ", "frags");
797 FOREACH(Scores, true, {
799 if(it != ps_secondary)
800 if(scores_label(it) != "")
801 s = strcat(s, " ", scores_label(it));
803 if(ps_secondary != ps_primary)
804 s = strcat(s, " ", scores_label(ps_secondary));
805 s = strcat(s, " ", scores_label(ps_primary));
806 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
813 hud_fontsize = HUD_GetFontsize("hud_fontsize");
815 for(i = 1; i < argc - 1; ++i)
818 bool nocomplain = false;
819 if(substring(str, 0, 1) == "?")
822 str = substring(str, 1, strlen(str) - 1);
825 slash = strstrofs(str, "/", 0);
828 pattern = substring(str, 0, slash);
829 str = substring(str, slash + 1, strlen(str) - (slash + 1));
831 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
835 str = strtolower(str);
836 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
837 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
842 // fields without a label (not networked via the score system)
843 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
844 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
845 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
846 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
847 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
848 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
849 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
850 default: // fields with a label
852 // map alternative labels
853 if (str == "damage") str = "dmg";
854 if (str == "damagetaken") str = "dmgtaken";
856 FOREACH(Scores, true, {
857 if (str == strtolower(scores_label(it))) {
859 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
863 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
864 if(!nocomplain && str != "fps") // server can disable the fps field
865 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
867 strfree(sbt_field_title[sbt_num_fields]);
868 sbt_field_size[sbt_num_fields] = 0;
872 sbt_field[sbt_num_fields] = j;
875 if(j == ps_secondary)
876 have_secondary = true;
881 if(sbt_num_fields >= MAX_SBT_FIELDS)
885 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
887 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
888 have_secondary = true;
889 if(ps_primary == ps_secondary)
890 have_secondary = true;
891 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
893 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
897 strfree(sbt_field_title[sbt_num_fields]);
898 for(i = sbt_num_fields; i > 0; --i)
900 sbt_field_title[i] = sbt_field_title[i-1];
901 sbt_field_size[i] = sbt_field_size[i-1];
902 sbt_field[i] = sbt_field[i-1];
904 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
905 sbt_field[0] = SP_NAME;
907 LOG_INFO("fixed missing field 'name'");
911 strfree(sbt_field_title[sbt_num_fields]);
912 for(i = sbt_num_fields; i > 1; --i)
914 sbt_field_title[i] = sbt_field_title[i-1];
915 sbt_field_size[i] = sbt_field_size[i-1];
916 sbt_field[i] = sbt_field[i-1];
918 sbt_field_title[1] = strzone("|");
919 sbt_field[1] = SP_SEPARATOR;
920 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
922 LOG_INFO("fixed missing field '|'");
925 else if(!have_separator)
927 strcpy(sbt_field_title[sbt_num_fields], "|");
928 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
929 sbt_field[sbt_num_fields] = SP_SEPARATOR;
931 LOG_INFO("fixed missing field '|'");
935 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
936 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
937 sbt_field[sbt_num_fields] = ps_secondary;
939 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
943 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
944 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
945 sbt_field[sbt_num_fields] = ps_primary;
947 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
951 sbt_field[sbt_num_fields] = SP_END;
954 string Scoreboard_AddPlayerId(string pl_name, entity pl)
956 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
957 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
958 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
962 vector sbt_field_rgb;
963 string sbt_field_icon0;
964 string sbt_field_icon1;
965 string sbt_field_icon2;
966 vector sbt_field_icon0_rgb;
967 vector sbt_field_icon1_rgb;
968 vector sbt_field_icon2_rgb;
969 string Scoreboard_GetName(entity pl)
971 if(ready_waiting && pl.ready)
973 sbt_field_icon0 = "gfx/scoreboard/player_ready";
977 int f = entcs_GetClientColors(pl.sv_entnum);
979 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
980 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
981 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
982 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
983 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
986 return entcs_GetName(pl.sv_entnum);
989 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
991 float tmp, num, denom;
994 sbt_field_rgb = '1 1 1';
995 sbt_field_icon0 = "";
996 sbt_field_icon1 = "";
997 sbt_field_icon2 = "";
998 sbt_field_icon0_rgb = '1 1 1';
999 sbt_field_icon1_rgb = '1 1 1';
1000 sbt_field_icon2_rgb = '1 1 1';
1001 int rounds_played = 0;
1003 rounds_played = pl.(scores(SP_ROUNDS_PL));
1008 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1009 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1013 tmp = max(0, min(220, f-80)) / 220;
1014 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1020 f = pl.ping_packetloss;
1021 tmp = pl.ping_movementloss;
1022 if(f == 0 && tmp == 0)
1024 str = ftos(ceil(f * 100));
1026 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1027 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1028 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1032 str = Scoreboard_GetName(pl);
1033 if (autocvar_hud_panel_scoreboard_playerid)
1034 str = Scoreboard_AddPlayerId(str, pl);
1038 f = pl.(scores(SP_KILLS));
1039 f -= pl.(scores(SP_SUICIDES));
1041 return sprintf("%.1f", f / rounds_played);
1045 num = pl.(scores(SP_KILLS));
1046 denom = pl.(scores(SP_DEATHS));
1049 sbt_field_rgb = '0 1 0';
1051 str = sprintf("%.1f", num / rounds_played);
1053 str = sprintf("%d", num);
1054 } else if(num <= 0) {
1055 sbt_field_rgb = '1 0 0';
1057 str = sprintf("%.2f", num / (denom * rounds_played));
1059 str = sprintf("%.1f", num / denom);
1063 str = sprintf("%.2f", num / (denom * rounds_played));
1065 str = sprintf("%.1f", num / denom);
1070 f = pl.(scores(SP_KILLS));
1071 f -= pl.(scores(SP_DEATHS));
1074 sbt_field_rgb = '0 1 0';
1076 sbt_field_rgb = '1 1 1';
1078 sbt_field_rgb = '1 0 0';
1081 return sprintf("%.1f", f / rounds_played);
1086 float elo = pl.(scores(SP_ELO));
1088 case -1: return "...";
1089 case -2: return _("N/A");
1090 default: return ftos(elo);
1096 float fps = pl.(scores(SP_FPS));
1099 sbt_field_rgb = '1 1 1';
1100 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1102 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1103 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1108 return ftos(pl.(scores(field)));
1110 case SP_DMG: case SP_DMGTAKEN:
1112 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1113 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1117 float strafe_efficiency = pl.(scores(field)) / 1000;
1118 if(strafe_efficiency < -1) return "";
1119 sbt_field_rgb = '1 1 1' - (strafe_efficiency > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_efficiency);
1120 return sprintf("%.1f%%", strafe_efficiency * 100);
1123 case SP_CTS_STARTSPEED:
1124 case SP_CTS_AVGSPEED:
1125 case SP_CTS_TOPSPEED:
1127 float speed = pl.(scores(field)) * GetSpeedUnitFactor(autocvar_hud_speed_unit);
1128 if(speed < 0) return "";
1129 return sprintf("%d%s", speed, GetSpeedUnit(autocvar_hud_speed_unit));
1132 default: case SP_SCORE:
1133 tmp = pl.(scores(field));
1134 f = scores_flags(field);
1135 if(field == ps_primary)
1136 sbt_field_rgb = '1 1 0';
1137 else if(field == ps_secondary)
1138 sbt_field_rgb = '0 1 1';
1140 sbt_field_rgb = '1 1 1';
1141 return ScoreString(f, tmp, rounds_played);
1146 float sbt_fixcolumnwidth_len;
1147 float sbt_fixcolumnwidth_iconlen;
1148 float sbt_fixcolumnwidth_marginlen;
1150 string Scoreboard_FixColumnWidth(int i, string str)
1156 sbt_fixcolumnwidth_iconlen = 0;
1158 if(sbt_field_icon0 != "")
1160 sz = draw_getimagesize(sbt_field_icon0);
1162 if(sbt_fixcolumnwidth_iconlen < f)
1163 sbt_fixcolumnwidth_iconlen = f;
1166 if(sbt_field_icon1 != "")
1168 sz = draw_getimagesize(sbt_field_icon1);
1170 if(sbt_fixcolumnwidth_iconlen < f)
1171 sbt_fixcolumnwidth_iconlen = f;
1174 if(sbt_field_icon2 != "")
1176 sz = draw_getimagesize(sbt_field_icon2);
1178 if(sbt_fixcolumnwidth_iconlen < f)
1179 sbt_fixcolumnwidth_iconlen = f;
1182 if(sbt_fixcolumnwidth_iconlen != 0)
1184 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1185 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1188 sbt_fixcolumnwidth_marginlen = 0;
1190 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1193 float remaining_space = 0;
1194 for(j = 0; j < sbt_num_fields; ++j)
1196 if (sbt_field[i] != SP_SEPARATOR)
1197 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1198 sbt_field_size[i] = panel_size.x - remaining_space;
1200 if (sbt_fixcolumnwidth_iconlen != 0)
1201 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1202 float namesize = panel_size.x - remaining_space;
1203 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1204 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1206 max_namesize = vid_conwidth - remaining_space;
1209 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1211 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1212 if(sbt_field_size[i] < f)
1213 sbt_field_size[i] = f;
1218 void Scoreboard_initFieldSizes()
1220 for(int i = 0; i < sbt_num_fields; ++i)
1222 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1223 Scoreboard_FixColumnWidth(i, "");
1227 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1230 vector column_dim = eY * panel_size.y;
1232 column_dim.y -= 1.25 * hud_fontsize.y;
1233 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1234 pos.x += hud_fontsize.x * 0.5;
1235 for(i = 0; i < sbt_num_fields; ++i)
1237 if(sbt_field[i] == SP_SEPARATOR)
1239 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1242 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1243 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 pos.x += column_dim.x;
1246 if(sbt_field[i] == SP_SEPARATOR)
1248 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1249 for(i = sbt_num_fields - 1; i > 0; --i)
1251 if(sbt_field[i] == SP_SEPARATOR)
1254 pos.x -= sbt_field_size[i];
1259 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1260 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1263 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1264 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1265 pos.x -= hud_fontsize.x;
1269 pos.x = panel_pos.x;
1270 pos.y += 1.25 * hud_fontsize.y;
1274 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1276 TC(bool, is_self); TC(int, pl_number);
1278 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1280 vector h_pos = item_pos;
1281 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1282 // alternated rows highlighting
1283 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1285 if (pl == scoreboard_selected_player)
1286 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1289 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1290 else if((sbt_highlight) && (!(pl_number % 2)))
1291 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1293 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1295 vector pos = item_pos;
1296 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1298 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1300 pos.x += hud_fontsize.x * 0.5;
1301 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1302 vector tmp = '0 0 0';
1304 PlayerScoreField field;
1305 for(i = 0; i < sbt_num_fields; ++i)
1307 field = sbt_field[i];
1308 if(field == SP_SEPARATOR)
1311 if(is_spec && field != SP_NAME && field != SP_PING) {
1312 pos.x += sbt_field_size[i] + hud_fontsize.x;
1315 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1316 str = Scoreboard_FixColumnWidth(i, str);
1318 pos.x += sbt_field_size[i] + hud_fontsize.x;
1320 if(field == SP_NAME) {
1321 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1322 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1324 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1325 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1328 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1329 if(sbt_field_icon0 != "")
1330 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1331 if(sbt_field_icon1 != "")
1332 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1333 if(sbt_field_icon2 != "")
1334 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1337 if(sbt_field[i] == SP_SEPARATOR)
1339 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1340 for(i = sbt_num_fields-1; i > 0; --i)
1342 field = sbt_field[i];
1343 if(field == SP_SEPARATOR)
1346 if(is_spec && field != SP_NAME && field != SP_PING) {
1347 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1351 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1352 str = Scoreboard_FixColumnWidth(i, str);
1354 if(field == SP_NAME) {
1355 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1356 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1358 tmp.x = sbt_fixcolumnwidth_len;
1359 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1362 tmp.x = sbt_field_size[i];
1363 if(sbt_field_icon0 != "")
1364 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1365 if(sbt_field_icon1 != "")
1366 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1367 if(sbt_field_icon2 != "")
1368 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1369 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1374 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1377 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1380 vector h_pos = item_pos;
1381 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1383 bool complete = (this_team == NUM_SPECTATOR);
1386 if((sbt_highlight) && (!(pl_number % 2)))
1387 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1389 vector pos = item_pos;
1390 pos.x += hud_fontsize.x * 0.5;
1391 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1393 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1395 width_limit -= stringwidth("...", false, hud_fontsize);
1396 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1397 static float max_name_width = 0;
1399 float fieldsize = 0;
1400 float min_fieldsize = 0;
1401 float fieldpadding = hud_fontsize.x * 0.25;
1402 if(this_team == NUM_SPECTATOR)
1404 if(autocvar_hud_panel_scoreboard_spectators_showping)
1405 min_fieldsize = stringwidth("999", false, hud_fontsize);
1407 else if(autocvar_hud_panel_scoreboard_others_showscore)
1408 min_fieldsize = stringwidth("99", false, hud_fontsize);
1409 for(i = 0; pl; pl = pl.sort_next)
1411 if(pl.team != this_team)
1413 if(pl == ignored_pl)
1417 if(this_team == NUM_SPECTATOR)
1419 if(autocvar_hud_panel_scoreboard_spectators_showping)
1420 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1422 else if(autocvar_hud_panel_scoreboard_others_showscore)
1423 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1425 string str = entcs_GetName(pl.sv_entnum);
1426 if (autocvar_hud_panel_scoreboard_playerid)
1427 str = Scoreboard_AddPlayerId(str, pl);
1428 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1429 float column_width = stringwidth(str, true, hud_fontsize);
1430 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1432 if(column_width > max_name_width)
1433 max_name_width = column_width;
1434 column_width = max_name_width;
1438 fieldsize = stringwidth(field, false, hud_fontsize);
1439 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1442 if(pos.x + column_width > width_limit)
1447 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1453 pos.y += hud_fontsize.y * 1.25;
1457 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1459 if (pl == scoreboard_selected_player)
1461 h_size.x = column_width + hud_fontsize.x * 0.25;
1462 h_size.y = hud_fontsize.y;
1463 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1467 vector name_pos = pos;
1468 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1469 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1470 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1473 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1474 h_size.y = hud_fontsize.y;
1475 vector field_pos = pos;
1476 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1477 field_pos.x += column_width - h_size.x;
1479 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1480 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1481 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1485 h_size.x = column_width + hud_fontsize.x * 0.25;
1486 h_size.y = hud_fontsize.y;
1487 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1489 pos.x += column_width;
1490 pos.x += hud_fontsize.x;
1492 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1495 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1497 int max_players = 999;
1498 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1500 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1503 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1504 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1505 height /= team_count;
1508 height -= panel_bg_padding * 2; // - padding
1509 max_players = floor(height / (hud_fontsize.y * 1.25));
1510 if(max_players <= 1)
1512 if(max_players == tm.team_size)
1517 entity me = playerslots[current_player];
1519 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1520 panel_size.y += panel_bg_padding * 2;
1522 vector scoreboard_selected_hl_pos = pos;
1523 vector scoreboard_selected_hl_size = '0 0 0';
1524 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1525 scoreboard_selected_hl_size.y = panel_size.y;
1529 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1530 if(panel.current_panel_bg != "0")
1531 end_pos.y += panel_bg_border * 2;
1533 if(panel_bg_padding)
1535 panel_pos += '1 1 0' * panel_bg_padding;
1536 panel_size -= '2 2 0' * panel_bg_padding;
1540 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1544 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1546 pos.y += 1.25 * hud_fontsize.y;
1549 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1551 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1554 // print header row and highlight columns
1555 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1557 // fill the table and draw the rows
1558 bool is_self = false;
1559 bool self_shown = false;
1561 for(pl = players.sort_next; pl; pl = pl.sort_next)
1563 if(pl.team != tm.team)
1565 if(i == max_players - 2 && pl != me)
1567 if(!self_shown && me.team == tm.team)
1569 Scoreboard_DrawItem(pos, rgb, me, true, i);
1571 pos.y += 1.25 * hud_fontsize.y;
1575 if(i >= max_players - 1)
1577 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1580 is_self = (pl.sv_entnum == current_player);
1581 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1584 pos.y += 1.25 * hud_fontsize.y;
1588 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1590 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1592 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1593 _alpha *= panel_fg_alpha;
1595 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1599 panel_size.x += panel_bg_padding * 2; // restore initial width
1603 bool Scoreboard_WouldDraw()
1605 if (scoreboard_ui_enabled)
1607 if (scoreboard_ui_disabling)
1609 if (scoreboard_fade_alpha == 0)
1610 HUD_Scoreboard_UI_Disable_Instantly();
1613 if (intermission && scoreboard_ui_enabled == 2)
1615 HUD_Scoreboard_UI_Disable_Instantly();
1620 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1622 else if (QuickMenu_IsOpened())
1624 else if (HUD_Radar_Clickable())
1626 else if (sb_showscores) // set by +showscores engine command
1628 else if (intermission == 1)
1630 else if (intermission == 2)
1632 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1633 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1637 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1642 float average_accuracy;
1643 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1645 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1647 WepSet weapons_stat = WepSet_GetFromStat();
1648 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1649 int disownedcnt = 0;
1651 FOREACH(Weapons, it != WEP_Null, {
1652 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1654 WepSet set = it.m_wepset;
1655 if(it.spawnflags & WEP_TYPE_OTHER)
1660 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1662 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1669 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1670 if (weapon_cnt <= 0) return pos;
1673 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1675 int columns = ceil(weapon_cnt / rows);
1677 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1678 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1679 float height = weapon_height + hud_fontsize.y;
1681 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);
1682 pos.y += 1.25 * hud_fontsize.y;
1683 if(panel.current_panel_bg != "0")
1684 pos.y += panel_bg_border;
1687 panel_size.y = height * rows;
1688 panel_size.y += panel_bg_padding * 2;
1690 float panel_bg_alpha_save = panel_bg_alpha;
1691 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1693 panel_bg_alpha = panel_bg_alpha_save;
1695 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1696 if(panel.current_panel_bg != "0")
1697 end_pos.y += panel_bg_border * 2;
1699 if(panel_bg_padding)
1701 panel_pos += '1 1 0' * panel_bg_padding;
1702 panel_size -= '2 2 0' * panel_bg_padding;
1706 vector tmp = panel_size;
1708 float weapon_width = tmp.x / columns / rows;
1711 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1715 // column highlighting
1716 for (int i = 0; i < columns; ++i)
1718 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);
1721 for (int i = 0; i < rows; ++i)
1722 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1725 average_accuracy = 0;
1726 int weapons_with_stats = 0;
1728 pos.x += weapon_width / 2;
1730 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1733 Accuracy_LoadColors();
1735 float oldposx = pos.x;
1739 FOREACH(Weapons, it != WEP_Null, {
1740 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1742 WepSet set = it.m_wepset;
1743 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1745 if (it.spawnflags & WEP_TYPE_OTHER)
1749 if (weapon_stats >= 0)
1750 weapon_alpha = sbt_fg_alpha;
1752 weapon_alpha = 0.2 * sbt_fg_alpha;
1755 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1757 if (weapon_stats >= 0) {
1758 weapons_with_stats += 1;
1759 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1761 string s = sprintf("%d%%", weapon_stats * 100);
1762 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1764 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1765 rgb = Accuracy_GetColor(weapon_stats);
1767 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1769 tmpos.x += weapon_width * rows;
1770 pos.x += weapon_width * rows;
1771 if (rows == 2 && column == columns - 1) {
1779 if (weapons_with_stats)
1780 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1782 panel_size.x += panel_bg_padding * 2; // restore initial width
1787 bool is_item_filtered(entity it)
1789 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1791 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1794 if (it.instanceOfArmor || it.instanceOfHealth)
1796 int ha_mask = floor(mask) % 10;
1799 default: return false;
1800 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1801 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1802 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1803 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1806 if (it.instanceOfAmmo)
1808 int ammo_mask = floor(mask / 10) % 10;
1809 return (ammo_mask == 1);
1814 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1816 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1818 int disowned_cnt = 0;
1819 int uninteresting_cnt = 0;
1820 IL_EACH(default_order_items, true, {
1821 int q = g_inventory.inv_items[it.m_id];
1822 //q = 1; // debug: display all items
1823 if (is_item_filtered(it))
1824 ++uninteresting_cnt;
1828 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1829 int n = items_cnt - disowned_cnt;
1830 if (n <= 0) return pos;
1832 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1833 int columns = max(6, ceil(n / rows));
1835 float item_height = hud_fontsize.y * 2.3;
1836 float height = item_height + hud_fontsize.y;
1838 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1839 pos.y += 1.25 * hud_fontsize.y;
1840 if(panel.current_panel_bg != "0")
1841 pos.y += panel_bg_border;
1844 panel_size.y = height * rows;
1845 panel_size.y += panel_bg_padding * 2;
1847 float panel_bg_alpha_save = panel_bg_alpha;
1848 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1850 panel_bg_alpha = panel_bg_alpha_save;
1852 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1853 if(panel.current_panel_bg != "0")
1854 end_pos.y += panel_bg_border * 2;
1856 if(panel_bg_padding)
1858 panel_pos += '1 1 0' * panel_bg_padding;
1859 panel_size -= '2 2 0' * panel_bg_padding;
1863 vector tmp = panel_size;
1865 float item_width = tmp.x / columns / rows;
1868 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1872 // column highlighting
1873 for (int i = 0; i < columns; ++i)
1875 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);
1878 for (int i = 0; i < rows; ++i)
1879 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1883 pos.x += item_width / 2;
1885 float oldposx = pos.x;
1889 IL_EACH(default_order_items, !is_item_filtered(it), {
1890 int n = g_inventory.inv_items[it.m_id];
1891 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1892 if (n <= 0) continue;
1893 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);
1895 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1896 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1897 tmpos.x += item_width * rows;
1898 pos.x += item_width * rows;
1899 if (rows == 2 && column == columns - 1) {
1907 panel_size.x += panel_bg_padding * 2; // restore initial width
1912 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1914 pos.x += hud_fontsize.x * 0.25;
1915 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1916 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1917 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1919 pos.y += hud_fontsize.y;
1924 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1925 float stat_secrets_found, stat_secrets_total;
1926 float stat_monsters_killed, stat_monsters_total;
1930 // get monster stats
1931 stat_monsters_killed = STAT(MONSTERS_KILLED);
1932 stat_monsters_total = STAT(MONSTERS_TOTAL);
1934 // get secrets stats
1935 stat_secrets_found = STAT(SECRETS_FOUND);
1936 stat_secrets_total = STAT(SECRETS_TOTAL);
1938 // get number of rows
1939 if(stat_secrets_total)
1941 if(stat_monsters_total)
1944 // if no rows, return
1948 // draw table header
1949 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1950 pos.y += 1.25 * hud_fontsize.y;
1951 if(panel.current_panel_bg != "0")
1952 pos.y += panel_bg_border;
1955 panel_size.y = hud_fontsize.y * rows;
1956 panel_size.y += panel_bg_padding * 2;
1959 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1960 if(panel.current_panel_bg != "0")
1961 end_pos.y += panel_bg_border * 2;
1963 if(panel_bg_padding)
1965 panel_pos += '1 1 0' * panel_bg_padding;
1966 panel_size -= '2 2 0' * panel_bg_padding;
1970 vector tmp = panel_size;
1973 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1976 if(stat_monsters_total)
1978 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1979 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1983 if(stat_secrets_total)
1985 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1986 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1989 panel_size.x += panel_bg_padding * 2; // restore initial width
1993 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1996 RANKINGS_RECEIVED_CNT = 0;
1997 for (i=RANKINGS_CNT-1; i>=0; --i)
1999 ++RANKINGS_RECEIVED_CNT;
2001 if (RANKINGS_RECEIVED_CNT == 0)
2004 vector hl_rgb = rgb + '0.5 0.5 0.5';
2006 vector scoreboard_selected_hl_pos = pos;
2008 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2009 pos.y += 1.25 * hud_fontsize.y;
2010 if(panel.current_panel_bg != "0")
2011 pos.y += panel_bg_border;
2013 vector scoreboard_selected_hl_size = '0 0 0';
2014 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2015 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2020 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2022 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2027 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2029 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2033 float ranksize = 3 * hud_fontsize.x;
2034 float timesize = 5 * hud_fontsize.x;
2035 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2036 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2037 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2040 rankings_cnt = RANKINGS_RECEIVED_CNT;
2041 rankings_rows = ceil(rankings_cnt / rankings_columns);
2044 // expand name column to fill the entire row
2045 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2046 namesize += available_space;
2047 columnsize.x += available_space;
2049 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2050 panel_size.y += panel_bg_padding * 2;
2051 scoreboard_selected_hl_size.y += panel_size.y;
2055 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2056 if(panel.current_panel_bg != "0")
2057 end_pos.y += panel_bg_border * 2;
2059 if(panel_bg_padding)
2061 panel_pos += '1 1 0' * panel_bg_padding;
2062 panel_size -= '2 2 0' * panel_bg_padding;
2068 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2070 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2072 int column = 0, j = 0;
2073 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2074 int start_item = rankings_start_column * rankings_rows;
2075 for(i = start_item; i < start_item + rankings_cnt; ++i)
2077 int t = grecordtime[i];
2081 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2082 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2083 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2084 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2086 str = count_ordinal(i+1);
2087 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2088 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2089 str = ColorTranslateRGB(grecordholder[i]);
2091 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2092 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2094 pos.y += 1.25 * hud_fontsize.y;
2096 if(j >= rankings_rows)
2100 pos.x += panel_size.x / rankings_columns;
2101 pos.y = panel_pos.y;
2104 strfree(zoned_name_self);
2106 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2108 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2109 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2112 panel_size.x += panel_bg_padding * 2; // restore initial width
2116 bool have_weapon_stats;
2117 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2119 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2121 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2124 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2125 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2131 if (!have_weapon_stats)
2133 FOREACH(Weapons, it != WEP_Null, {
2134 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2135 if (weapon_stats >= 0)
2137 have_weapon_stats = true;
2141 if (!have_weapon_stats)
2148 bool have_item_stats;
2149 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2151 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2153 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2156 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2157 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2163 if (!have_item_stats)
2165 IL_EACH(default_order_items, true, {
2166 if (!is_item_filtered(it))
2168 int q = g_inventory.inv_items[it.m_id];
2169 //q = 1; // debug: display all items
2172 have_item_stats = true;
2177 if (!have_item_stats)
2184 vector Scoreboard_Spectators_Draw(vector pos) {
2189 for(pl = players.sort_next; pl; pl = pl.sort_next)
2191 if(pl.team == NUM_SPECTATOR)
2193 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2194 if(tm.team == NUM_SPECTATOR)
2196 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2197 draw_beginBoldFont();
2198 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2200 pos.y += 1.25 * hud_fontsize.y;
2202 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2203 pos.y += 1.25 * hud_fontsize.y;
2208 if (str != "") // if there's at least one spectator
2209 pos.y += 0.5 * hud_fontsize.y;
2214 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2216 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2217 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2218 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2219 (s_label == "score") ? CTX(_("SCO^points")) :
2220 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2223 void Scoreboard_Draw()
2225 if(!autocvar__hud_configure)
2227 if(!hud_draw_maximized) return;
2229 // frametime checks allow to toggle the scoreboard even when the game is paused
2230 if(scoreboard_active) {
2231 if (scoreboard_fade_alpha == 0)
2232 scoreboard_time = time;
2233 if(hud_configure_menu_open == 1)
2234 scoreboard_fade_alpha = 1;
2235 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2236 if (scoreboard_fadeinspeed && frametime)
2237 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2239 scoreboard_fade_alpha = 1;
2240 if(hud_fontsize_str != autocvar_hud_fontsize)
2242 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2243 Scoreboard_initFieldSizes();
2244 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2248 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2249 if (scoreboard_fadeoutspeed && frametime)
2250 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2252 scoreboard_fade_alpha = 0;
2255 if (!scoreboard_fade_alpha)
2257 scoreboard_acc_fade_alpha = 0;
2258 scoreboard_itemstats_fade_alpha = 0;
2263 scoreboard_fade_alpha = 0;
2265 if (autocvar_hud_panel_scoreboard_dynamichud)
2268 HUD_Scale_Disable();
2270 if(scoreboard_fade_alpha <= 0)
2272 panel_fade_alpha *= scoreboard_fade_alpha;
2273 HUD_Panel_LoadCvars();
2275 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2276 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2277 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2278 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2279 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2280 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2281 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2283 // don't overlap with con_notify
2284 if(!autocvar__hud_configure)
2285 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2287 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2288 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2289 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2290 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2291 panel_pos.x = scoreboard_left;
2292 panel_size.x = fixed_scoreboard_width;
2294 Scoreboard_UpdatePlayerTeams();
2296 scoreboard_top = panel_pos.y;
2297 vector pos = panel_pos;
2302 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2304 // Begin of Game Info Section
2305 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2306 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2308 // Game Info: Game Type
2309 if (scoreboard_ui_enabled == 2)
2310 str = _("Team Selection");
2312 str = MapInfo_Type_ToText(gametype);
2313 draw_beginBoldFont();
2314 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);
2317 pos.y += sb_gameinfo_type_fontsize.y;
2318 // Game Info: Game Detail
2319 if (scoreboard_ui_enabled == 2)
2321 if (scoreboard_selected_team)
2322 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2324 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2325 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);
2327 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2328 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2329 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);
2333 float tl = STAT(TIMELIMIT);
2334 float fl = STAT(FRAGLIMIT);
2335 float ll = STAT(LEADLIMIT);
2336 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2339 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2340 if(!gametype.m_hidelimits)
2345 str = strcat(str, "^7 / "); // delimiter
2346 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2350 if(tl > 0 || fl > 0)
2353 if (ll_and_fl && fl > 0)
2354 str = strcat(str, "^7 & ");
2356 str = strcat(str, "^7 / ");
2358 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2361 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
2362 // map name and player count
2366 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2367 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2368 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2370 // End of Game Info Section
2372 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2373 if(panel.current_panel_bg != "0")
2374 pos.y += panel_bg_border;
2376 // Draw the scoreboard
2377 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2380 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2384 vector panel_bg_color_save = panel_bg_color;
2385 vector team_score_baseoffset;
2386 vector team_size_baseoffset;
2387 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2389 // put team score to the left of scoreboard (and team size to the right)
2390 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2391 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2392 if(panel.current_panel_bg != "0")
2394 team_score_baseoffset.x -= panel_bg_border;
2395 team_size_baseoffset.x += panel_bg_border;
2400 // put team score to the right of scoreboard (and team size to the left)
2401 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2402 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2403 if(panel.current_panel_bg != "0")
2405 team_score_baseoffset.x += panel_bg_border;
2406 team_size_baseoffset.x -= panel_bg_border;
2410 int team_size_total = 0;
2411 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2413 // calculate team size total (sum of all team sizes)
2414 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2415 if(tm.team != NUM_SPECTATOR)
2416 team_size_total += tm.team_size;
2419 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2421 if(tm.team == NUM_SPECTATOR)
2426 draw_beginBoldFont();
2427 vector rgb = Team_ColorRGB(tm.team);
2428 str = ftos(tm.(teamscores(ts_primary)));
2429 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2431 // team score on the left (default)
2432 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2436 // team score on the right
2437 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2439 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2441 // team size (if set to show on the side)
2442 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2444 // calculate the starting position for the whole team size info string
2445 str = sprintf("%d/%d", tm.team_size, team_size_total);
2446 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2448 // team size on the left
2449 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2453 // team size on the right
2454 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2456 str = sprintf("%d", tm.team_size);
2457 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2458 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2459 str = sprintf("/%d", team_size_total);
2460 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2464 // secondary score, e.g. keyhunt
2465 if(ts_primary != ts_secondary)
2467 str = ftos(tm.(teamscores(ts_secondary)));
2468 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2471 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2476 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2479 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2482 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2483 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2484 else if(panel_bg_color_team > 0)
2485 panel_bg_color = rgb * panel_bg_color_team;
2487 panel_bg_color = rgb;
2488 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2490 panel_bg_color = panel_bg_color_save;
2494 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2495 if(tm.team != NUM_SPECTATOR)
2498 // display it anyway
2499 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2502 // draw scoreboard spectators before accuracy and item stats
2503 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2504 pos = Scoreboard_Spectators_Draw(pos);
2507 // draw accuracy and item stats
2508 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2509 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2510 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2511 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2513 // draw scoreboard spectators after accuracy and item stats and before rankings
2514 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2515 pos = Scoreboard_Spectators_Draw(pos);
2518 if(MUTATOR_CALLHOOK(ShowRankings)) {
2519 string ranktitle = M_ARGV(0, string);
2520 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2521 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2522 if(race_speedaward_alltimebest)
2525 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2529 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2530 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2531 str = strcat(str, " / ");
2533 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2534 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2535 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2536 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2538 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2543 // draw scoreboard spectators after rankings
2544 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2545 pos = Scoreboard_Spectators_Draw(pos);
2548 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2550 // draw scoreboard spectators after mapstats
2551 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2552 pos = Scoreboard_Spectators_Draw(pos);
2556 // print information about respawn status
2557 float respawn_time = STAT(RESPAWN_TIME);
2558 if(!intermission && respawn_time)
2560 if(respawn_time < 0)
2562 // a negative number means we are awaiting respawn, time value is still the same
2563 respawn_time *= -1; // remove mark now that we checked it
2565 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2566 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2568 str = sprintf(_("^1Respawning in ^3%s^1..."),
2569 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2570 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2572 count_seconds(ceil(respawn_time - time))
2576 else if(time < respawn_time)
2578 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2579 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2580 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2582 count_seconds(ceil(respawn_time - time))
2586 else if(time >= respawn_time)
2587 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2589 pos.y += 1.2 * hud_fontsize.y;
2590 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2593 pos.y += hud_fontsize.y;
2594 if (scoreboard_fade_alpha < 1)
2595 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2596 else if (pos.y != scoreboard_bottom)
2598 if (pos.y > scoreboard_bottom)
2599 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2601 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2606 if (scoreboard_fade_alpha == 1)
2608 if (scoreboard_bottom > 0.95 * vid_conheight)
2609 rankings_rows = max(1, rankings_rows - 1);
2610 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2611 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2613 rankings_cnt = rankings_rows * rankings_columns;