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 "startspeed": if (!mode) return CTX(_("SCO^start speed")); else LOG_HELP(strcat("^3", "startspeed", " ^7", _("Start speed (CTS)")));
158 case "strafe": if (!mode) return CTX(_("SCO^strafe")); else LOG_HELP(strcat("^3", "strafe", " ^7", _("Strafe efficiency (CTS)")));
159 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
160 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
161 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
162 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
163 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
164 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
165 default: return label;
170 bool scoreboard_ui_disabling;
171 void HUD_Scoreboard_UI_Disable()
173 scoreboard_ui_disabling = true;
174 scoreboard_showscores = false;
177 void HUD_Scoreboard_UI_Disable_Instantly()
179 scoreboard_ui_disabling = false;
180 scoreboard_ui_enabled = 0;
181 scoreboard_selected_panel = 0;
182 scoreboard_selected_player = NULL;
183 scoreboard_selected_team = NULL;
186 // mode: 0 normal, 1 team selection
187 void Scoreboard_UI_Enable(int mode)
193 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
196 // release player's pressed keys as they aren't released elsewhere
197 // in particular jump needs to be released as it may open the team selection
198 // (when server detects jump has been pressed it sends the command to open the team selection)
199 Release_Common_Keys();
200 scoreboard_ui_enabled = 2;
201 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
205 if (scoreboard_ui_enabled == 1)
207 scoreboard_ui_enabled = 1;
208 scoreboard_selected_panel = SB_PANEL_FIRST;
210 scoreboard_selected_player = NULL;
211 scoreboard_selected_team = NULL;
212 scoreboard_selected_panel_time = time;
215 int rankings_start_column;
216 int rankings_rows = 0;
217 int rankings_columns = 0;
218 int rankings_cnt = 0;
219 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
223 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
228 mousepos.x = nPrimary;
229 mousepos.y = nSecondary;
236 // at this point bInputType can only be 0 or 1 (key pressed or released)
237 bool key_pressed = (bInputType == 0);
239 // ESC to exit (TAB-ESC works too)
240 if(nPrimary == K_ESCAPE)
244 HUD_Scoreboard_UI_Disable();
248 // block any input while a menu dialog is fading
249 if(autocvar__menu_alpha)
255 // allow console bind to work
256 string con_keys = findkeysforcommand("toggleconsole", 0);
257 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
259 bool hit_con_bind = false;
261 for (i = 0; i < keys; ++i)
263 if(nPrimary == stof(argv(i)))
268 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
269 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
270 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
271 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
274 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
275 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
276 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
277 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
280 if(nPrimary == K_TAB)
284 if (scoreboard_ui_enabled == 2)
286 if (hudShiftState & S_SHIFT)
289 goto downarrow_action;
292 if (hudShiftState & S_SHIFT)
294 --scoreboard_selected_panel;
295 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
296 --scoreboard_selected_panel;
297 if (scoreboard_selected_panel < SB_PANEL_FIRST)
298 scoreboard_selected_panel = SB_PANEL_MAX;
302 ++scoreboard_selected_panel;
303 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
304 ++scoreboard_selected_panel;
305 if (scoreboard_selected_panel > SB_PANEL_MAX)
306 scoreboard_selected_panel = SB_PANEL_FIRST;
309 scoreboard_selected_panel_time = time;
311 else if(nPrimary == K_DOWNARROW)
315 LABEL(downarrow_action);
316 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
318 if (scoreboard_ui_enabled == 2)
320 entity curr_team = NULL;
321 bool scoreboard_selected_team_found = false;
322 if (!scoreboard_selected_team)
323 scoreboard_selected_team_found = true;
325 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
327 if(tm.team == NUM_SPECTATOR)
330 if (scoreboard_selected_team_found)
332 if (scoreboard_selected_team == tm)
333 scoreboard_selected_team_found = true;
336 if (curr_team == scoreboard_selected_team) // loop reached the last team
338 scoreboard_selected_team = curr_team;
343 entity curr_pl = NULL;
344 bool scoreboard_selected_player_found = false;
345 if (!scoreboard_selected_player)
346 scoreboard_selected_player_found = true;
348 for(tm = teams.sort_next; tm; tm = tm.sort_next)
350 if(tm.team != NUM_SPECTATOR)
351 for(pl = players.sort_next; pl; pl = pl.sort_next)
353 if(pl.team != tm.team)
356 if (scoreboard_selected_player_found)
358 if (scoreboard_selected_player == pl)
359 scoreboard_selected_player_found = true;
363 if (curr_pl == scoreboard_selected_player) // loop reached the last player
365 scoreboard_selected_player = curr_pl;
369 else if(nPrimary == K_UPARROW)
373 LABEL(uparrow_action);
374 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
376 if (scoreboard_ui_enabled == 2)
378 entity prev_team = NULL;
379 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
381 if(tm.team == NUM_SPECTATOR)
383 if (tm == scoreboard_selected_team)
388 scoreboard_selected_team = prev_team;
392 entity prev_pl = NULL;
394 for(tm = teams.sort_next; tm; tm = tm.sort_next)
396 if(tm.team != NUM_SPECTATOR)
397 for(pl = players.sort_next; pl; pl = pl.sort_next)
399 if(pl.team != tm.team)
401 if (pl == scoreboard_selected_player)
407 scoreboard_selected_player = prev_pl;
411 else if(nPrimary == K_RIGHTARROW)
415 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
416 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
418 else if(nPrimary == K_LEFTARROW)
422 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
423 rankings_start_column = max(rankings_start_column - 1, 0);
425 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
429 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
431 if (scoreboard_ui_enabled == 2)
434 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
437 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
438 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
439 HUD_Scoreboard_UI_Disable();
441 else if (scoreboard_selected_player)
442 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
445 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
449 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
451 switch (scoreboard_selected_columns_layout)
454 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
456 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
457 scoreboard_selected_columns_layout = 1;
462 localcmd(sprintf("scoreboard_columns_set default\n"));
463 scoreboard_selected_columns_layout = 2;
466 localcmd(sprintf("scoreboard_columns_set all\n"));
467 scoreboard_selected_columns_layout = 0;
472 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
476 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
478 if (scoreboard_selected_player)
480 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
481 HUD_Scoreboard_UI_Disable();
485 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
489 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
491 if (scoreboard_selected_player)
492 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
495 else if(hit_con_bind || nPrimary == K_PAUSE)
501 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
502 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
504 void Scoreboard_InitScores()
508 ps_primary = ps_secondary = NULL;
509 ts_primary = ts_secondary = -1;
510 FOREACH(Scores, true, {
511 if(scores_flags(it) & SFL_NOT_SORTABLE)
513 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
514 if(f == SFL_SORT_PRIO_PRIMARY)
516 if(f == SFL_SORT_PRIO_SECONDARY)
519 if(ps_secondary == NULL)
520 ps_secondary = ps_primary;
522 for(i = 0; i < MAX_TEAMSCORE; ++i)
524 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
525 if(f == SFL_SORT_PRIO_PRIMARY)
527 if(f == SFL_SORT_PRIO_SECONDARY)
530 if(ts_secondary == -1)
531 ts_secondary = ts_primary;
533 Cmd_Scoreboard_SetFields(0);
537 void Scoreboard_UpdatePlayerTeams()
539 static float update_time;
540 if (time <= update_time)
547 for(pl = players.sort_next; pl; pl = pl.sort_next)
549 numplayers += pl.team != NUM_SPECTATOR;
551 int Team = entcs_GetScoreTeam(pl.sv_entnum);
552 if(SetTeam(pl, Team))
555 Scoreboard_UpdatePlayerPos(pl);
559 pl = players.sort_next;
564 print(strcat("PNUM: ", ftos(num), "\n"));
569 int Scoreboard_CompareScore(int vl, int vr, int f)
571 TC(int, vl); TC(int, vr); TC(int, f);
572 if(f & SFL_ZERO_IS_WORST)
574 if(vl == 0 && vr != 0)
576 if(vl != 0 && vr == 0)
580 return IS_INCREASING(f);
582 return IS_DECREASING(f);
586 float Scoreboard_ComparePlayerScores(entity left, entity right)
588 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
589 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
596 if(vl == NUM_SPECTATOR)
598 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
600 if(!left.gotscores && right.gotscores)
605 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
606 if (res >= 0) return res;
608 if (ps_secondary && ps_secondary != ps_primary)
610 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
611 if (res >= 0) return res;
614 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
615 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
616 if (res >= 0) return res;
619 if (left.sv_entnum < right.sv_entnum)
625 void Scoreboard_UpdatePlayerPos(entity player)
628 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
630 SORT_SWAP(player, ent);
632 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
634 SORT_SWAP(ent, player);
638 float Scoreboard_CompareTeamScores(entity left, entity right)
640 if(left.team == NUM_SPECTATOR)
642 if(right.team == NUM_SPECTATOR)
647 for(int i = -2; i < MAX_TEAMSCORE; ++i)
651 if (fld_idx == -1) fld_idx = ts_primary;
652 else if (ts_secondary == ts_primary) continue;
653 else fld_idx = ts_secondary;
658 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
661 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
662 if (r >= 0) return r;
665 if (left.team < right.team)
671 void Scoreboard_UpdateTeamPos(entity Team)
674 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
676 SORT_SWAP(Team, ent);
678 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
680 SORT_SWAP(ent, Team);
684 void Cmd_Scoreboard_Help()
686 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
687 LOG_HELP(_("Usage:"));
688 LOG_HELP("^2scoreboard_columns_set ^3default");
689 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
690 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
691 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
692 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
693 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
694 LOG_HELP(_("The following field names are recognized (case insensitive):"));
700 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
701 "of game types, then a slash, to make the field show up only in these\n"
702 "or in all but these game types. You can also specify 'all' as a\n"
703 "field to show all fields available for the current game mode."));
706 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
707 "include/exclude ALL teams/noteams game modes."));
710 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
711 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
712 "right of the vertical bar aligned to the right."));
713 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
714 "other gamemodes except DM."));
717 // NOTE: adding a gametype with ? to not warn for an optional field
718 // make sure it's excluded in a previous exclusive rule, if any
719 // otherwise the previous exclusive rule warns anyway
720 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
721 #define SCOREBOARD_DEFAULT_COLUMNS \
722 "ping pl fps name |" \
723 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
724 " -teams,lms/deaths +ft,tdm/deaths" \
726 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
727 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
728 " +tdm,ft,dom,ons,as/teamkills"\
729 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
730 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
731 " +lms/lives +lms/rank" \
732 " +kh/kckills +kh/losses +kh/caps" \
733 " ?+rc/laps ?+rc/time ?+cts/strafe ?+cts/startspeed +rc,cts/fastest" \
734 " +as/objectives +nb/faults +nb/goals" \
735 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
736 " +dom/ticks +dom/takes" \
737 " -lms,rc,cts,inv,nb/score"
739 void Cmd_Scoreboard_SetFields(int argc)
744 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
748 return; // do nothing, we don't know gametype and scores yet
750 // sbt_fields uses strunzone on the titles!
751 if(!sbt_field_title[0])
752 for(i = 0; i < MAX_SBT_FIELDS; ++i)
753 sbt_field_title[i] = strzone("(null)");
755 // TODO: re enable with gametype dependant cvars?
756 if(argc < 3) // no arguments provided
757 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
760 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
764 if(argv(2) == "default" || argv(2) == "expand_default")
766 if(argv(2) == "expand_default")
767 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
768 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
770 else if(argv(2) == "all" || argv(2) == "ALL")
772 string s = "ping pl name |"; // scores without label (not really scores)
775 // scores without label
776 s = strcat(s, " ", "sum");
777 s = strcat(s, " ", "kdratio");
778 s = strcat(s, " ", "frags");
780 FOREACH(Scores, true, {
782 if(it != ps_secondary)
783 if(scores_label(it) != "")
784 s = strcat(s, " ", scores_label(it));
786 if(ps_secondary != ps_primary)
787 s = strcat(s, " ", scores_label(ps_secondary));
788 s = strcat(s, " ", scores_label(ps_primary));
789 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
796 hud_fontsize = HUD_GetFontsize("hud_fontsize");
798 for(i = 1; i < argc - 1; ++i)
801 bool nocomplain = false;
802 if(substring(str, 0, 1) == "?")
805 str = substring(str, 1, strlen(str) - 1);
808 slash = strstrofs(str, "/", 0);
811 pattern = substring(str, 0, slash);
812 str = substring(str, slash + 1, strlen(str) - (slash + 1));
814 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
818 str = strtolower(str);
819 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
820 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
825 // fields without a label (not networked via the score system)
826 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
827 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
828 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
829 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
830 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
831 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
832 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
833 default: // fields with a label
835 // map alternative labels
836 if (str == "damage") str = "dmg";
837 if (str == "damagetaken") str = "dmgtaken";
839 FOREACH(Scores, true, {
840 if (str == strtolower(scores_label(it))) {
842 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
846 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
847 if(!nocomplain && str != "fps") // server can disable the fps field
848 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
850 strfree(sbt_field_title[sbt_num_fields]);
851 sbt_field_size[sbt_num_fields] = 0;
855 sbt_field[sbt_num_fields] = j;
858 if(j == ps_secondary)
859 have_secondary = true;
864 if(sbt_num_fields >= MAX_SBT_FIELDS)
868 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
870 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
871 have_secondary = true;
872 if(ps_primary == ps_secondary)
873 have_secondary = true;
874 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
876 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
880 strfree(sbt_field_title[sbt_num_fields]);
881 for(i = sbt_num_fields; i > 0; --i)
883 sbt_field_title[i] = sbt_field_title[i-1];
884 sbt_field_size[i] = sbt_field_size[i-1];
885 sbt_field[i] = sbt_field[i-1];
887 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
888 sbt_field[0] = SP_NAME;
890 LOG_INFO("fixed missing field 'name'");
894 strfree(sbt_field_title[sbt_num_fields]);
895 for(i = sbt_num_fields; i > 1; --i)
897 sbt_field_title[i] = sbt_field_title[i-1];
898 sbt_field_size[i] = sbt_field_size[i-1];
899 sbt_field[i] = sbt_field[i-1];
901 sbt_field_title[1] = strzone("|");
902 sbt_field[1] = SP_SEPARATOR;
903 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
905 LOG_INFO("fixed missing field '|'");
908 else if(!have_separator)
910 strcpy(sbt_field_title[sbt_num_fields], "|");
911 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
912 sbt_field[sbt_num_fields] = SP_SEPARATOR;
914 LOG_INFO("fixed missing field '|'");
918 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
919 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
920 sbt_field[sbt_num_fields] = ps_secondary;
922 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
926 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
927 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
928 sbt_field[sbt_num_fields] = ps_primary;
930 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
934 sbt_field[sbt_num_fields] = SP_END;
937 string Scoreboard_AddPlayerId(string pl_name, entity pl)
939 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
940 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
941 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
945 vector sbt_field_rgb;
946 string sbt_field_icon0;
947 string sbt_field_icon1;
948 string sbt_field_icon2;
949 vector sbt_field_icon0_rgb;
950 vector sbt_field_icon1_rgb;
951 vector sbt_field_icon2_rgb;
952 string Scoreboard_GetName(entity pl)
954 if(ready_waiting && pl.ready)
956 sbt_field_icon0 = "gfx/scoreboard/player_ready";
960 int f = entcs_GetClientColors(pl.sv_entnum);
962 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
963 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
964 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
965 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
966 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
969 return entcs_GetName(pl.sv_entnum);
972 string Scoreboard_GetField(entity pl, PlayerScoreField field)
974 float tmp, num, denom;
977 sbt_field_rgb = '1 1 1';
978 sbt_field_icon0 = "";
979 sbt_field_icon1 = "";
980 sbt_field_icon2 = "";
981 sbt_field_icon0_rgb = '1 1 1';
982 sbt_field_icon1_rgb = '1 1 1';
983 sbt_field_icon2_rgb = '1 1 1';
988 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
989 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
993 tmp = max(0, min(220, f-80)) / 220;
994 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1000 f = pl.ping_packetloss;
1001 tmp = pl.ping_movementloss;
1002 if(f == 0 && tmp == 0)
1004 str = ftos(ceil(f * 100));
1006 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1007 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1008 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1012 str = Scoreboard_GetName(pl);
1013 if (autocvar_hud_panel_scoreboard_playerid)
1014 str = Scoreboard_AddPlayerId(str, pl);
1018 f = pl.(scores(SP_KILLS));
1019 f -= pl.(scores(SP_SUICIDES));
1023 num = pl.(scores(SP_KILLS));
1024 denom = pl.(scores(SP_DEATHS));
1027 sbt_field_rgb = '0 1 0';
1028 str = sprintf("%d", num);
1029 } else if(num <= 0) {
1030 sbt_field_rgb = '1 0 0';
1031 str = sprintf("%.1f", num/denom);
1033 str = sprintf("%.1f", num/denom);
1037 f = pl.(scores(SP_KILLS));
1038 f -= pl.(scores(SP_DEATHS));
1041 sbt_field_rgb = '0 1 0';
1043 sbt_field_rgb = '1 1 1';
1045 sbt_field_rgb = '1 0 0';
1051 float elo = pl.(scores(SP_ELO));
1053 case -1: return "...";
1054 case -2: return _("N/A");
1055 default: return ftos(elo);
1061 float fps = pl.(scores(SP_FPS));
1064 sbt_field_rgb = '1 1 1';
1065 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1067 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1068 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1072 case SP_DMG: case SP_DMGTAKEN:
1073 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1077 float strafe_efficiency = pl.(scores(field)) / 10000;
1078 if(strafe_efficiency < -1) return "";
1079 sbt_field_rgb = '1 1 1' - (strafe_efficiency > 0 ? '1 0 1' : '0 1 1') * fabs(strafe_efficiency);
1080 return sprintf("%.2f%%", strafe_efficiency * 100);
1083 case SP_CTS_STARTSPEED:
1085 float startspeed = pl.(scores(field)) / 100;
1086 if(startspeed < 0) return "";
1087 return sprintf("%.2f qu/s", startspeed);
1090 default: case SP_SCORE:
1091 tmp = pl.(scores(field));
1092 f = scores_flags(field);
1093 if(field == ps_primary)
1094 sbt_field_rgb = '1 1 0';
1095 else if(field == ps_secondary)
1096 sbt_field_rgb = '0 1 1';
1098 sbt_field_rgb = '1 1 1';
1099 return ScoreString(f, tmp);
1104 float sbt_fixcolumnwidth_len;
1105 float sbt_fixcolumnwidth_iconlen;
1106 float sbt_fixcolumnwidth_marginlen;
1108 string Scoreboard_FixColumnWidth(int i, string str)
1114 sbt_fixcolumnwidth_iconlen = 0;
1116 if(sbt_field_icon0 != "")
1118 sz = draw_getimagesize(sbt_field_icon0);
1120 if(sbt_fixcolumnwidth_iconlen < f)
1121 sbt_fixcolumnwidth_iconlen = f;
1124 if(sbt_field_icon1 != "")
1126 sz = draw_getimagesize(sbt_field_icon1);
1128 if(sbt_fixcolumnwidth_iconlen < f)
1129 sbt_fixcolumnwidth_iconlen = f;
1132 if(sbt_field_icon2 != "")
1134 sz = draw_getimagesize(sbt_field_icon2);
1136 if(sbt_fixcolumnwidth_iconlen < f)
1137 sbt_fixcolumnwidth_iconlen = f;
1140 if(sbt_fixcolumnwidth_iconlen != 0)
1142 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1143 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1146 sbt_fixcolumnwidth_marginlen = 0;
1148 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1151 float remaining_space = 0;
1152 for(j = 0; j < sbt_num_fields; ++j)
1154 if (sbt_field[i] != SP_SEPARATOR)
1155 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1156 sbt_field_size[i] = panel_size.x - remaining_space;
1158 if (sbt_fixcolumnwidth_iconlen != 0)
1159 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1160 float namesize = panel_size.x - remaining_space;
1161 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1162 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1164 max_namesize = vid_conwidth - remaining_space;
1167 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1169 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1170 if(sbt_field_size[i] < f)
1171 sbt_field_size[i] = f;
1176 void Scoreboard_initFieldSizes()
1178 for(int i = 0; i < sbt_num_fields; ++i)
1180 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1181 Scoreboard_FixColumnWidth(i, "");
1185 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1188 vector column_dim = eY * panel_size.y;
1190 column_dim.y -= 1.25 * hud_fontsize.y;
1191 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1192 pos.x += hud_fontsize.x * 0.5;
1193 for(i = 0; i < sbt_num_fields; ++i)
1195 if(sbt_field[i] == SP_SEPARATOR)
1197 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1200 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1201 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1202 pos.x += column_dim.x;
1204 if(sbt_field[i] == SP_SEPARATOR)
1206 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1207 for(i = sbt_num_fields - 1; i > 0; --i)
1209 if(sbt_field[i] == SP_SEPARATOR)
1212 pos.x -= sbt_field_size[i];
1217 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1218 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1221 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1222 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1223 pos.x -= hud_fontsize.x;
1227 pos.x = panel_pos.x;
1228 pos.y += 1.25 * hud_fontsize.y;
1232 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1234 TC(bool, is_self); TC(int, pl_number);
1236 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1238 vector h_pos = item_pos;
1239 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1240 // alternated rows highlighting
1241 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1243 if (pl == scoreboard_selected_player)
1244 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1247 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1248 else if((sbt_highlight) && (!(pl_number % 2)))
1249 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1251 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1253 vector pos = item_pos;
1254 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1256 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1258 pos.x += hud_fontsize.x * 0.5;
1259 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1260 vector tmp = '0 0 0';
1262 PlayerScoreField field;
1263 for(i = 0; i < sbt_num_fields; ++i)
1265 field = sbt_field[i];
1266 if(field == SP_SEPARATOR)
1269 if(is_spec && field != SP_NAME && field != SP_PING) {
1270 pos.x += sbt_field_size[i] + hud_fontsize.x;
1273 str = Scoreboard_GetField(pl, field);
1274 str = Scoreboard_FixColumnWidth(i, str);
1276 pos.x += sbt_field_size[i] + hud_fontsize.x;
1278 if(field == SP_NAME) {
1279 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1280 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1282 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1283 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1286 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1287 if(sbt_field_icon0 != "")
1288 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1289 if(sbt_field_icon1 != "")
1290 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1291 if(sbt_field_icon2 != "")
1292 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1295 if(sbt_field[i] == SP_SEPARATOR)
1297 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1298 for(i = sbt_num_fields-1; i > 0; --i)
1300 field = sbt_field[i];
1301 if(field == SP_SEPARATOR)
1304 if(is_spec && field != SP_NAME && field != SP_PING) {
1305 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1309 str = Scoreboard_GetField(pl, field);
1310 str = Scoreboard_FixColumnWidth(i, str);
1312 if(field == SP_NAME) {
1313 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1314 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1316 tmp.x = sbt_fixcolumnwidth_len;
1317 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1320 tmp.x = sbt_field_size[i];
1321 if(sbt_field_icon0 != "")
1322 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1323 if(sbt_field_icon1 != "")
1324 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1325 if(sbt_field_icon2 != "")
1326 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1327 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1332 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1335 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1338 vector h_pos = item_pos;
1339 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1341 bool complete = (this_team == NUM_SPECTATOR);
1344 if((sbt_highlight) && (!(pl_number % 2)))
1345 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1347 vector pos = item_pos;
1348 pos.x += hud_fontsize.x * 0.5;
1349 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1351 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1353 width_limit -= stringwidth("...", false, hud_fontsize);
1354 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1355 static float max_name_width = 0;
1357 float fieldsize = 0;
1358 float min_fieldsize = 0;
1359 float fieldpadding = hud_fontsize.x * 0.25;
1360 if(this_team == NUM_SPECTATOR)
1362 if(autocvar_hud_panel_scoreboard_spectators_showping)
1363 min_fieldsize = stringwidth("999", false, hud_fontsize);
1365 else if(autocvar_hud_panel_scoreboard_others_showscore)
1366 min_fieldsize = stringwidth("99", false, hud_fontsize);
1367 for(i = 0; pl; pl = pl.sort_next)
1369 if(pl.team != this_team)
1371 if(pl == ignored_pl)
1375 if(this_team == NUM_SPECTATOR)
1377 if(autocvar_hud_panel_scoreboard_spectators_showping)
1378 field = Scoreboard_GetField(pl, SP_PING);
1380 else if(autocvar_hud_panel_scoreboard_others_showscore)
1381 field = Scoreboard_GetField(pl, SP_SCORE);
1383 string str = entcs_GetName(pl.sv_entnum);
1384 if (autocvar_hud_panel_scoreboard_playerid)
1385 str = Scoreboard_AddPlayerId(str, pl);
1386 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1387 float column_width = stringwidth(str, true, hud_fontsize);
1388 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1390 if(column_width > max_name_width)
1391 max_name_width = column_width;
1392 column_width = max_name_width;
1396 fieldsize = stringwidth(field, false, hud_fontsize);
1397 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1400 if(pos.x + column_width > width_limit)
1405 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1410 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1411 pos.y += hud_fontsize.y * 1.25;
1415 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1417 if (pl == scoreboard_selected_player)
1419 h_size.x = column_width + hud_fontsize.x * 0.25;
1420 h_size.y = hud_fontsize.y;
1421 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1425 vector name_pos = pos;
1426 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1427 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1428 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1431 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1432 h_size.y = hud_fontsize.y;
1433 vector field_pos = pos;
1434 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1435 field_pos.x += column_width - h_size.x;
1437 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1438 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1439 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1443 h_size.x = column_width + hud_fontsize.x * 0.25;
1444 h_size.y = hud_fontsize.y;
1445 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1447 pos.x += column_width;
1448 pos.x += hud_fontsize.x;
1450 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1453 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1455 int max_players = 999;
1456 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1458 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1461 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1462 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1463 height /= team_count;
1466 height -= panel_bg_padding * 2; // - padding
1467 max_players = floor(height / (hud_fontsize.y * 1.25));
1468 if(max_players <= 1)
1470 if(max_players == tm.team_size)
1475 entity me = playerslots[current_player];
1477 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1478 panel_size.y += panel_bg_padding * 2;
1480 vector scoreboard_selected_hl_pos = pos;
1481 vector scoreboard_selected_hl_size = '0 0 0';
1482 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1483 scoreboard_selected_hl_size.y = panel_size.y;
1487 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1488 if(panel.current_panel_bg != "0")
1489 end_pos.y += panel_bg_border * 2;
1491 if(panel_bg_padding)
1493 panel_pos += '1 1 0' * panel_bg_padding;
1494 panel_size -= '2 2 0' * panel_bg_padding;
1498 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1502 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1504 pos.y += 1.25 * hud_fontsize.y;
1507 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1509 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1512 // print header row and highlight columns
1513 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1515 // fill the table and draw the rows
1516 bool is_self = false;
1517 bool self_shown = false;
1519 for(pl = players.sort_next; pl; pl = pl.sort_next)
1521 if(pl.team != tm.team)
1523 if(i == max_players - 2 && pl != me)
1525 if(!self_shown && me.team == tm.team)
1527 Scoreboard_DrawItem(pos, rgb, me, true, i);
1529 pos.y += 1.25 * hud_fontsize.y;
1533 if(i >= max_players - 1)
1535 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1538 is_self = (pl.sv_entnum == current_player);
1539 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1542 pos.y += 1.25 * hud_fontsize.y;
1546 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1548 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1550 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1551 _alpha *= panel_fg_alpha;
1553 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1557 panel_size.x += panel_bg_padding * 2; // restore initial width
1561 bool Scoreboard_WouldDraw()
1563 if (scoreboard_ui_enabled)
1565 if (scoreboard_ui_disabling)
1567 if (scoreboard_fade_alpha == 0)
1568 HUD_Scoreboard_UI_Disable_Instantly();
1571 if (intermission && scoreboard_ui_enabled == 2)
1573 HUD_Scoreboard_UI_Disable_Instantly();
1578 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1580 else if (QuickMenu_IsOpened())
1582 else if (HUD_Radar_Clickable())
1584 else if (scoreboard_showscores)
1586 else if (intermission == 1)
1588 else if (intermission == 2)
1590 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1591 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1595 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1600 float average_accuracy;
1601 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1603 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1605 WepSet weapons_stat = WepSet_GetFromStat();
1606 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1607 int disownedcnt = 0;
1609 FOREACH(Weapons, it != WEP_Null, {
1610 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1612 WepSet set = it.m_wepset;
1613 if(it.spawnflags & WEP_TYPE_OTHER)
1618 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1620 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1627 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1628 if (weapon_cnt <= 0) return pos;
1631 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1633 int columns = ceil(weapon_cnt / rows);
1635 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1636 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1637 float height = weapon_height + hud_fontsize.y;
1639 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);
1640 pos.y += 1.25 * hud_fontsize.y;
1641 if(panel.current_panel_bg != "0")
1642 pos.y += panel_bg_border;
1645 panel_size.y = height * rows;
1646 panel_size.y += panel_bg_padding * 2;
1648 float panel_bg_alpha_save = panel_bg_alpha;
1649 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1651 panel_bg_alpha = panel_bg_alpha_save;
1653 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1654 if(panel.current_panel_bg != "0")
1655 end_pos.y += panel_bg_border * 2;
1657 if(panel_bg_padding)
1659 panel_pos += '1 1 0' * panel_bg_padding;
1660 panel_size -= '2 2 0' * panel_bg_padding;
1664 vector tmp = panel_size;
1666 float weapon_width = tmp.x / columns / rows;
1669 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1673 // column highlighting
1674 for (int i = 0; i < columns; ++i)
1676 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);
1679 for (int i = 0; i < rows; ++i)
1680 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1683 average_accuracy = 0;
1684 int weapons_with_stats = 0;
1686 pos.x += weapon_width / 2;
1688 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1691 Accuracy_LoadColors();
1693 float oldposx = pos.x;
1697 FOREACH(Weapons, it != WEP_Null, {
1698 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1700 WepSet set = it.m_wepset;
1701 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1703 if (it.spawnflags & WEP_TYPE_OTHER)
1707 if (weapon_stats >= 0)
1708 weapon_alpha = sbt_fg_alpha;
1710 weapon_alpha = 0.2 * sbt_fg_alpha;
1713 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1715 if (weapon_stats >= 0) {
1716 weapons_with_stats += 1;
1717 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1719 string s = sprintf("%d%%", weapon_stats * 100);
1720 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1722 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1723 rgb = Accuracy_GetColor(weapon_stats);
1725 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1727 tmpos.x += weapon_width * rows;
1728 pos.x += weapon_width * rows;
1729 if (rows == 2 && column == columns - 1) {
1737 if (weapons_with_stats)
1738 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1740 panel_size.x += panel_bg_padding * 2; // restore initial width
1745 bool is_item_filtered(entity it)
1747 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1749 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1752 if (it.instanceOfArmor || it.instanceOfHealth)
1754 int ha_mask = floor(mask) % 10;
1757 default: return false;
1758 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1759 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1760 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1761 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1764 if (it.instanceOfAmmo)
1766 int ammo_mask = floor(mask / 10) % 10;
1767 return (ammo_mask == 1);
1772 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1774 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1776 int disowned_cnt = 0;
1777 int uninteresting_cnt = 0;
1778 IL_EACH(default_order_items, true, {
1779 int q = g_inventory.inv_items[it.m_id];
1780 //q = 1; // debug: display all items
1781 if (is_item_filtered(it))
1782 ++uninteresting_cnt;
1786 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1787 int n = items_cnt - disowned_cnt;
1788 if (n <= 0) return pos;
1790 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1791 int columns = max(6, ceil(n / rows));
1793 float item_height = hud_fontsize.y * 2.3;
1794 float height = item_height + hud_fontsize.y;
1796 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1797 pos.y += 1.25 * hud_fontsize.y;
1798 if(panel.current_panel_bg != "0")
1799 pos.y += panel_bg_border;
1802 panel_size.y = height * rows;
1803 panel_size.y += panel_bg_padding * 2;
1805 float panel_bg_alpha_save = panel_bg_alpha;
1806 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1808 panel_bg_alpha = panel_bg_alpha_save;
1810 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1811 if(panel.current_panel_bg != "0")
1812 end_pos.y += panel_bg_border * 2;
1814 if(panel_bg_padding)
1816 panel_pos += '1 1 0' * panel_bg_padding;
1817 panel_size -= '2 2 0' * panel_bg_padding;
1821 vector tmp = panel_size;
1823 float item_width = tmp.x / columns / rows;
1826 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1830 // column highlighting
1831 for (int i = 0; i < columns; ++i)
1833 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);
1836 for (int i = 0; i < rows; ++i)
1837 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1841 pos.x += item_width / 2;
1843 float oldposx = pos.x;
1847 IL_EACH(default_order_items, !is_item_filtered(it), {
1848 int n = g_inventory.inv_items[it.m_id];
1849 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1850 if (n <= 0) continue;
1851 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);
1853 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1854 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1855 tmpos.x += item_width * rows;
1856 pos.x += item_width * rows;
1857 if (rows == 2 && column == columns - 1) {
1865 panel_size.x += panel_bg_padding * 2; // restore initial width
1870 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1872 pos.x += hud_fontsize.x * 0.25;
1873 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1874 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1875 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1877 pos.y += hud_fontsize.y;
1882 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1883 float stat_secrets_found, stat_secrets_total;
1884 float stat_monsters_killed, stat_monsters_total;
1888 // get monster stats
1889 stat_monsters_killed = STAT(MONSTERS_KILLED);
1890 stat_monsters_total = STAT(MONSTERS_TOTAL);
1892 // get secrets stats
1893 stat_secrets_found = STAT(SECRETS_FOUND);
1894 stat_secrets_total = STAT(SECRETS_TOTAL);
1896 // get number of rows
1897 if(stat_secrets_total)
1899 if(stat_monsters_total)
1902 // if no rows, return
1906 // draw table header
1907 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1908 pos.y += 1.25 * hud_fontsize.y;
1909 if(panel.current_panel_bg != "0")
1910 pos.y += panel_bg_border;
1913 panel_size.y = hud_fontsize.y * rows;
1914 panel_size.y += panel_bg_padding * 2;
1917 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1918 if(panel.current_panel_bg != "0")
1919 end_pos.y += panel_bg_border * 2;
1921 if(panel_bg_padding)
1923 panel_pos += '1 1 0' * panel_bg_padding;
1924 panel_size -= '2 2 0' * panel_bg_padding;
1928 vector tmp = panel_size;
1931 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1934 if(stat_monsters_total)
1936 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1937 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1941 if(stat_secrets_total)
1943 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1944 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1947 panel_size.x += panel_bg_padding * 2; // restore initial width
1951 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1954 RANKINGS_RECEIVED_CNT = 0;
1955 for (i=RANKINGS_CNT-1; i>=0; --i)
1957 ++RANKINGS_RECEIVED_CNT;
1959 if (RANKINGS_RECEIVED_CNT == 0)
1962 vector hl_rgb = rgb + '0.5 0.5 0.5';
1964 vector scoreboard_selected_hl_pos = pos;
1966 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1967 pos.y += 1.25 * hud_fontsize.y;
1968 if(panel.current_panel_bg != "0")
1969 pos.y += panel_bg_border;
1971 vector scoreboard_selected_hl_size = '0 0 0';
1972 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1973 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1978 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1980 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1985 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1987 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1991 float ranksize = 3 * hud_fontsize.x;
1992 float timesize = 5 * hud_fontsize.x;
1993 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1994 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1995 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1998 rankings_cnt = RANKINGS_RECEIVED_CNT;
1999 rankings_rows = ceil(rankings_cnt / rankings_columns);
2002 // expand name column to fill the entire row
2003 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2004 namesize += available_space;
2005 columnsize.x += available_space;
2007 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2008 panel_size.y += panel_bg_padding * 2;
2009 scoreboard_selected_hl_size.y += panel_size.y;
2013 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2014 if(panel.current_panel_bg != "0")
2015 end_pos.y += panel_bg_border * 2;
2017 if(panel_bg_padding)
2019 panel_pos += '1 1 0' * panel_bg_padding;
2020 panel_size -= '2 2 0' * panel_bg_padding;
2026 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2028 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2030 int column = 0, j = 0;
2031 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2032 int start_item = rankings_start_column * rankings_rows;
2033 for(i = start_item; i < start_item + rankings_cnt; ++i)
2035 int t = grecordtime[i];
2039 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2040 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2041 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2042 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2044 str = count_ordinal(i+1);
2045 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2046 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2047 str = ColorTranslateRGB(grecordholder[i]);
2049 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2050 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2052 pos.y += 1.25 * hud_fontsize.y;
2054 if(j >= rankings_rows)
2058 pos.x += panel_size.x / rankings_columns;
2059 pos.y = panel_pos.y;
2062 strfree(zoned_name_self);
2064 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2066 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2067 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2070 panel_size.x += panel_bg_padding * 2; // restore initial width
2074 bool have_weapon_stats;
2075 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2077 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2079 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2082 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2083 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2089 if (!have_weapon_stats)
2091 FOREACH(Weapons, it != WEP_Null, {
2092 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2093 if (weapon_stats >= 0)
2095 have_weapon_stats = true;
2099 if (!have_weapon_stats)
2106 bool have_item_stats;
2107 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2109 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2111 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2114 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2115 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2121 if (!have_item_stats)
2123 IL_EACH(default_order_items, true, {
2124 if (!is_item_filtered(it))
2126 int q = g_inventory.inv_items[it.m_id];
2127 //q = 1; // debug: display all items
2130 have_item_stats = true;
2135 if (!have_item_stats)
2142 vector Scoreboard_Spectators_Draw(vector pos) {
2147 for(pl = players.sort_next; pl; pl = pl.sort_next)
2149 if(pl.team == NUM_SPECTATOR)
2151 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2152 if(tm.team == NUM_SPECTATOR)
2154 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2155 draw_beginBoldFont();
2156 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2158 pos.y += 1.25 * hud_fontsize.y;
2160 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2161 pos.y += 1.25 * hud_fontsize.y;
2166 if (str != "") // if there's at least one spectator
2167 pos.y += 0.5 * hud_fontsize.y;
2172 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2174 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2175 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2176 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit),
2177 (s_label == "score") ? CTX(_("SCO^points")) :
2178 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2181 void Scoreboard_Draw()
2183 if(!autocvar__hud_configure)
2185 if(!hud_draw_maximized) return;
2187 // frametime checks allow to toggle the scoreboard even when the game is paused
2188 if(scoreboard_active) {
2189 if (scoreboard_fade_alpha == 0)
2190 scoreboard_time = time;
2191 if(hud_configure_menu_open == 1)
2192 scoreboard_fade_alpha = 1;
2193 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2194 if (scoreboard_fadeinspeed && frametime)
2195 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2197 scoreboard_fade_alpha = 1;
2198 if(hud_fontsize_str != autocvar_hud_fontsize)
2200 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2201 Scoreboard_initFieldSizes();
2202 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2206 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2207 if (scoreboard_fadeoutspeed && frametime)
2208 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2210 scoreboard_fade_alpha = 0;
2213 if (!scoreboard_fade_alpha)
2215 scoreboard_acc_fade_alpha = 0;
2216 scoreboard_itemstats_fade_alpha = 0;
2221 scoreboard_fade_alpha = 0;
2223 if (autocvar_hud_panel_scoreboard_dynamichud)
2226 HUD_Scale_Disable();
2228 if(scoreboard_fade_alpha <= 0)
2230 panel_fade_alpha *= scoreboard_fade_alpha;
2231 HUD_Panel_LoadCvars();
2233 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2234 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2235 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2236 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2237 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2238 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2239 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2241 // don't overlap with con_notify
2242 if(!autocvar__hud_configure)
2243 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2245 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2246 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2247 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2248 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2249 panel_pos.x = scoreboard_left;
2250 panel_size.x = fixed_scoreboard_width;
2252 Scoreboard_UpdatePlayerTeams();
2254 scoreboard_top = panel_pos.y;
2255 vector pos = panel_pos;
2260 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2262 // Begin of Game Info Section
2263 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2264 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2266 // Game Info: Game Type
2267 if (scoreboard_ui_enabled == 2)
2268 str = _("Team Selection");
2270 str = MapInfo_Type_ToText(gametype);
2271 draw_beginBoldFont();
2272 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);
2275 pos.y += sb_gameinfo_type_fontsize.y;
2276 // Game Info: Game Detail
2277 if (scoreboard_ui_enabled == 2)
2279 if (scoreboard_selected_team)
2280 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2282 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2283 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);
2285 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2286 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
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);
2291 float tl = STAT(TIMELIMIT);
2292 float fl = STAT(FRAGLIMIT);
2293 float ll = STAT(LEADLIMIT);
2294 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2297 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2298 if(!gametype.m_hidelimits)
2303 str = strcat(str, "^7 / "); // delimiter
2304 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2308 if(tl > 0 || fl > 0)
2311 if (ll_and_fl && fl > 0)
2312 str = strcat(str, "^7 & ");
2314 str = strcat(str, "^7 / ");
2316 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2319 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
2320 // map name and player count
2321 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2322 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2323 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2325 // End of Game Info Section
2327 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2328 if(panel.current_panel_bg != "0")
2329 pos.y += panel_bg_border;
2331 // Draw the scoreboard
2332 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2335 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2339 vector panel_bg_color_save = panel_bg_color;
2340 vector team_score_baseoffset;
2341 vector team_size_baseoffset;
2342 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2344 // put team score to the left of scoreboard (and team size to the right)
2345 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2346 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2347 if(panel.current_panel_bg != "0")
2349 team_score_baseoffset.x -= panel_bg_border;
2350 team_size_baseoffset.x += panel_bg_border;
2355 // put team score to the right of scoreboard (and team size to the left)
2356 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2357 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2358 if(panel.current_panel_bg != "0")
2360 team_score_baseoffset.x += panel_bg_border;
2361 team_size_baseoffset.x -= panel_bg_border;
2365 int team_size_total = 0;
2366 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2368 // calculate team size total (sum of all team sizes)
2369 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2370 if(tm.team != NUM_SPECTATOR)
2371 team_size_total += tm.team_size;
2374 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2376 if(tm.team == NUM_SPECTATOR)
2381 draw_beginBoldFont();
2382 vector rgb = Team_ColorRGB(tm.team);
2383 str = ftos(tm.(teamscores(ts_primary)));
2384 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2386 // team score on the left (default)
2387 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2391 // team score on the right
2392 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2394 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2396 // team size (if set to show on the side)
2397 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2399 // calculate the starting position for the whole team size info string
2400 str = sprintf("%d/%d", tm.team_size, team_size_total);
2401 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2403 // team size on the left
2404 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2408 // team size on the right
2409 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2411 str = sprintf("%d", tm.team_size);
2412 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2413 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2414 str = sprintf("/%d", team_size_total);
2415 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2419 // secondary score, e.g. keyhunt
2420 if(ts_primary != ts_secondary)
2422 str = ftos(tm.(teamscores(ts_secondary)));
2423 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2426 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2431 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2434 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2437 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2438 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2439 else if(panel_bg_color_team > 0)
2440 panel_bg_color = rgb * panel_bg_color_team;
2442 panel_bg_color = rgb;
2443 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2445 panel_bg_color = panel_bg_color_save;
2449 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2450 if(tm.team != NUM_SPECTATOR)
2453 // display it anyway
2454 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2457 // draw scoreboard spectators before accuracy and item stats
2458 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2459 pos = Scoreboard_Spectators_Draw(pos);
2462 // draw accuracy and item stats
2463 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2464 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2465 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2466 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2468 // draw scoreboard spectators after accuracy and item stats and before rankings
2469 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2470 pos = Scoreboard_Spectators_Draw(pos);
2473 if(MUTATOR_CALLHOOK(ShowRankings)) {
2474 string ranktitle = M_ARGV(0, string);
2475 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2476 if(race_speedaward_alltimebest)
2479 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2483 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2484 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2485 str = strcat(str, " / ");
2487 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2488 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2489 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2490 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2492 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2497 // draw scoreboard spectators after rankings
2498 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2499 pos = Scoreboard_Spectators_Draw(pos);
2502 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2504 // draw scoreboard spectators after mapstats
2505 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2506 pos = Scoreboard_Spectators_Draw(pos);
2510 // print information about respawn status
2511 float respawn_time = STAT(RESPAWN_TIME);
2512 if(!intermission && respawn_time)
2514 if(respawn_time < 0)
2516 // a negative number means we are awaiting respawn, time value is still the same
2517 respawn_time *= -1; // remove mark now that we checked it
2519 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2520 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2522 str = sprintf(_("^1Respawning in ^3%s^1..."),
2523 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2524 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2526 count_seconds(ceil(respawn_time - time))
2530 else if(time < respawn_time)
2532 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2533 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2534 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2536 count_seconds(ceil(respawn_time - time))
2540 else if(time >= respawn_time)
2541 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2543 pos.y += 1.2 * hud_fontsize.y;
2544 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2547 pos.y += hud_fontsize.y;
2548 if (scoreboard_fade_alpha < 1)
2549 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2550 else if (pos.y != scoreboard_bottom)
2552 if (pos.y > scoreboard_bottom)
2553 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2555 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2560 if (scoreboard_fade_alpha == 1)
2562 if (scoreboard_bottom > 0.95 * vid_conheight)
2563 rankings_rows = max(1, rankings_rows - 1);
2564 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2565 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2567 rankings_cnt = rankings_rows * rankings_columns;