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 "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
158 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
159 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
160 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
161 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
162 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
163 default: return label;
168 bool scoreboard_ui_disabling;
169 void HUD_Scoreboard_UI_Disable()
171 scoreboard_ui_disabling = true;
172 scoreboard_showscores = false;
175 void HUD_Scoreboard_UI_Disable_Instantly()
177 scoreboard_ui_disabling = false;
178 scoreboard_ui_enabled = 0;
179 scoreboard_selected_panel = 0;
180 scoreboard_selected_player = NULL;
181 scoreboard_selected_team = NULL;
184 // mode: 0 normal, 1 team selection
185 void Scoreboard_UI_Enable(int mode)
191 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
194 // release player's pressed keys as they aren't released elsewhere
195 // in particular jump needs to be released as it may open the team selection
196 // (when server detects jump has been pressed it sends the command to open the team selection)
197 Release_Common_Keys();
198 scoreboard_ui_enabled = 2;
199 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
203 if (scoreboard_ui_enabled == 1)
205 scoreboard_ui_enabled = 1;
206 scoreboard_selected_panel = SB_PANEL_FIRST;
208 scoreboard_selected_player = NULL;
209 scoreboard_selected_team = NULL;
210 scoreboard_selected_panel_time = time;
213 int rankings_start_column;
214 int rankings_rows = 0;
215 int rankings_columns = 0;
216 int rankings_cnt = 0;
217 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
221 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
226 mousepos.x = nPrimary;
227 mousepos.y = nSecondary;
234 // at this point bInputType can only be 0 or 1 (key pressed or released)
235 bool key_pressed = (bInputType == 0);
237 // ESC to exit (TAB-ESC works too)
238 if(nPrimary == K_ESCAPE)
242 HUD_Scoreboard_UI_Disable();
246 // block any input while a menu dialog is fading
247 if(autocvar__menu_alpha)
253 // allow console bind to work
254 string con_keys = findkeysforcommand("toggleconsole", 0);
255 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
257 bool hit_con_bind = false;
259 for (i = 0; i < keys; ++i)
261 if(nPrimary == stof(argv(i)))
266 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
267 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
268 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
269 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
272 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
273 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
274 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
275 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
278 if(nPrimary == K_TAB)
282 if (scoreboard_ui_enabled == 2)
284 if (hudShiftState & S_SHIFT)
287 goto downarrow_action;
290 if (hudShiftState & S_SHIFT)
292 --scoreboard_selected_panel;
293 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
294 --scoreboard_selected_panel;
295 if (scoreboard_selected_panel < SB_PANEL_FIRST)
296 scoreboard_selected_panel = SB_PANEL_MAX;
300 ++scoreboard_selected_panel;
301 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
302 ++scoreboard_selected_panel;
303 if (scoreboard_selected_panel > SB_PANEL_MAX)
304 scoreboard_selected_panel = SB_PANEL_FIRST;
307 scoreboard_selected_panel_time = time;
309 else if(nPrimary == K_DOWNARROW)
313 LABEL(downarrow_action);
314 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
316 if (scoreboard_ui_enabled == 2)
318 entity curr_team = NULL;
319 bool scoreboard_selected_team_found = false;
320 if (!scoreboard_selected_team)
321 scoreboard_selected_team_found = true;
323 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
325 if(tm.team == NUM_SPECTATOR)
328 if (scoreboard_selected_team_found)
330 if (scoreboard_selected_team == tm)
331 scoreboard_selected_team_found = true;
334 if (curr_team == scoreboard_selected_team) // loop reached the last team
336 scoreboard_selected_team = curr_team;
341 entity curr_pl = NULL;
342 bool scoreboard_selected_player_found = false;
343 if (!scoreboard_selected_player)
344 scoreboard_selected_player_found = true;
346 for(tm = teams.sort_next; tm; tm = tm.sort_next)
348 if(tm.team != NUM_SPECTATOR)
349 for(pl = players.sort_next; pl; pl = pl.sort_next)
351 if(pl.team != tm.team)
354 if (scoreboard_selected_player_found)
356 if (scoreboard_selected_player == pl)
357 scoreboard_selected_player_found = true;
361 if (curr_pl == scoreboard_selected_player) // loop reached the last player
363 scoreboard_selected_player = curr_pl;
367 else if(nPrimary == K_UPARROW)
371 LABEL(uparrow_action);
372 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
374 if (scoreboard_ui_enabled == 2)
376 entity prev_team = NULL;
377 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
379 if(tm.team == NUM_SPECTATOR)
381 if (tm == scoreboard_selected_team)
386 scoreboard_selected_team = prev_team;
390 entity prev_pl = NULL;
392 for(tm = teams.sort_next; tm; tm = tm.sort_next)
394 if(tm.team != NUM_SPECTATOR)
395 for(pl = players.sort_next; pl; pl = pl.sort_next)
397 if(pl.team != tm.team)
399 if (pl == scoreboard_selected_player)
405 scoreboard_selected_player = prev_pl;
409 else if(nPrimary == K_RIGHTARROW)
413 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
414 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
416 else if(nPrimary == K_LEFTARROW)
420 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
421 rankings_start_column = max(rankings_start_column - 1, 0);
423 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
427 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
429 if (scoreboard_ui_enabled == 2)
432 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
435 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
436 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
437 HUD_Scoreboard_UI_Disable();
439 else if (scoreboard_selected_player)
440 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
443 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
447 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
449 switch (scoreboard_selected_columns_layout)
452 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
454 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
455 scoreboard_selected_columns_layout = 1;
460 localcmd(sprintf("scoreboard_columns_set default\n"));
461 scoreboard_selected_columns_layout = 2;
464 localcmd(sprintf("scoreboard_columns_set all\n"));
465 scoreboard_selected_columns_layout = 0;
470 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
474 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
476 if (scoreboard_selected_player)
478 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
479 HUD_Scoreboard_UI_Disable();
483 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
487 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
489 if (scoreboard_selected_player)
490 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
493 else if(hit_con_bind || nPrimary == K_PAUSE)
499 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
500 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
502 #define SB_EXTRA_SORTING_FIELDS 5
503 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
504 void Scoreboard_InitScores()
508 ps_primary = ps_secondary = NULL;
509 ts_primary = ts_secondary = -1;
510 FOREACH(Scores, true, {
511 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
512 if(f == SFL_SORT_PRIO_PRIMARY)
514 if(f == SFL_SORT_PRIO_SECONDARY)
516 if(ps_primary == it || ps_secondary == it)
518 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
519 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
520 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
521 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
522 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
524 if(ps_secondary == NULL)
525 ps_secondary = ps_primary;
527 for(i = 0; i < MAX_TEAMSCORE; ++i)
529 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
530 if(f == SFL_SORT_PRIO_PRIMARY)
532 if(f == SFL_SORT_PRIO_SECONDARY)
535 if(ts_secondary == -1)
536 ts_secondary = ts_primary;
538 Cmd_Scoreboard_SetFields(0);
542 void Scoreboard_UpdatePlayerTeams()
544 static float update_time;
545 if (time <= update_time)
552 for(pl = players.sort_next; pl; pl = pl.sort_next)
554 numplayers += pl.team != NUM_SPECTATOR;
556 int Team = entcs_GetScoreTeam(pl.sv_entnum);
557 if(SetTeam(pl, Team))
560 Scoreboard_UpdatePlayerPos(pl);
564 pl = players.sort_next;
569 print(strcat("PNUM: ", ftos(num), "\n"));
574 int Scoreboard_CompareScore(int vl, int vr, int f)
576 TC(int, vl); TC(int, vr); TC(int, f);
577 if(f & SFL_ZERO_IS_WORST)
579 if(vl == 0 && vr != 0)
581 if(vl != 0 && vr == 0)
585 return IS_INCREASING(f);
587 return IS_DECREASING(f);
591 float Scoreboard_ComparePlayerScores(entity left, entity right)
593 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
594 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
601 if(vl == NUM_SPECTATOR)
603 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
605 if(!left.gotscores && right.gotscores)
612 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
616 if (!fld) fld = ps_primary;
617 else if (ps_secondary == ps_primary) continue;
618 else fld = ps_secondary;
622 fld = sb_extra_sorting_field[i];
623 if (fld == ps_primary || fld == ps_secondary) continue;
627 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
628 if (r >= 0) return r;
631 if (left.sv_entnum < right.sv_entnum)
637 void Scoreboard_UpdatePlayerPos(entity player)
640 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
642 SORT_SWAP(player, ent);
644 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
646 SORT_SWAP(ent, player);
650 float Scoreboard_CompareTeamScores(entity left, entity right)
652 if(left.team == NUM_SPECTATOR)
654 if(right.team == NUM_SPECTATOR)
659 for(int i = -2; i < MAX_TEAMSCORE; ++i)
663 if (fld_idx == -1) fld_idx = ts_primary;
664 else if (ts_secondary == ts_primary) continue;
665 else fld_idx = ts_secondary;
670 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
673 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
674 if (r >= 0) return r;
677 if (left.team < right.team)
683 void Scoreboard_UpdateTeamPos(entity Team)
686 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
688 SORT_SWAP(Team, ent);
690 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
692 SORT_SWAP(ent, Team);
696 void Cmd_Scoreboard_Help()
698 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
699 LOG_HELP(_("Usage:"));
700 LOG_HELP("^2scoreboard_columns_set ^3default");
701 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
702 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
703 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
704 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
705 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
706 LOG_HELP(_("The following field names are recognized (case insensitive):"));
712 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
713 "of game types, then a slash, to make the field show up only in these\n"
714 "or in all but these game types. You can also specify 'all' as a\n"
715 "field to show all fields available for the current game mode."));
718 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
719 "include/exclude ALL teams/noteams game modes."));
722 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
723 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
724 "right of the vertical bar aligned to the right."));
725 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
726 "other gamemodes except DM."));
729 // NOTE: adding a gametype with ? to not warn for an optional field
730 // make sure it's excluded in a previous exclusive rule, if any
731 // otherwise the previous exclusive rule warns anyway
732 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
733 #define SCOREBOARD_DEFAULT_COLUMNS \
734 "ping pl fps name |" \
735 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
736 " -teams,lms/deaths +ft,tdm/deaths" \
738 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
739 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
740 " +tdm,ft,dom,ons,as/teamkills"\
741 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
742 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
743 " +lms/lives +lms/rank" \
744 " +kh/kckills +kh/losses +kh/caps" \
745 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
746 " +as/objectives +nb/faults +nb/goals" \
747 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
748 " +dom/ticks +dom/takes" \
749 " -lms,rc,cts,inv,nb/score"
751 void Cmd_Scoreboard_SetFields(int argc)
756 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
760 return; // do nothing, we don't know gametype and scores yet
762 // sbt_fields uses strunzone on the titles!
763 if(!sbt_field_title[0])
764 for(i = 0; i < MAX_SBT_FIELDS; ++i)
765 sbt_field_title[i] = strzone("(null)");
767 // TODO: re enable with gametype dependant cvars?
768 if(argc < 3) // no arguments provided
769 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
772 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
776 if(argv(2) == "default" || argv(2) == "expand_default")
778 if(argv(2) == "expand_default")
779 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
780 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
782 else if(argv(2) == "all" || argv(2) == "ALL")
784 string s = "ping pl name |"; // scores without label (not really scores)
787 // scores without label
788 s = strcat(s, " ", "sum");
789 s = strcat(s, " ", "kdratio");
790 s = strcat(s, " ", "frags");
792 FOREACH(Scores, true, {
794 if(it != ps_secondary)
795 if(scores_label(it) != "")
796 s = strcat(s, " ", scores_label(it));
798 if(ps_secondary != ps_primary)
799 s = strcat(s, " ", scores_label(ps_secondary));
800 s = strcat(s, " ", scores_label(ps_primary));
801 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
808 hud_fontsize = HUD_GetFontsize("hud_fontsize");
810 for(i = 1; i < argc - 1; ++i)
813 bool nocomplain = false;
814 if(substring(str, 0, 1) == "?")
817 str = substring(str, 1, strlen(str) - 1);
820 slash = strstrofs(str, "/", 0);
823 pattern = substring(str, 0, slash);
824 str = substring(str, slash + 1, strlen(str) - (slash + 1));
826 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
830 str = strtolower(str);
831 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
832 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
837 // fields without a label (not networked via the score system)
838 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
839 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
840 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
841 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
842 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
843 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
844 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
845 default: // fields with a label
847 // map alternative labels
848 if (str == "damage") str = "dmg";
849 if (str == "damagetaken") str = "dmgtaken";
851 FOREACH(Scores, true, {
852 if (str == strtolower(scores_label(it))) {
854 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
858 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
859 if(!nocomplain && str != "fps") // server can disable the fps field
860 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
862 strfree(sbt_field_title[sbt_num_fields]);
863 sbt_field_size[sbt_num_fields] = 0;
867 sbt_field[sbt_num_fields] = j;
870 if(j == ps_secondary)
871 have_secondary = true;
876 if(sbt_num_fields >= MAX_SBT_FIELDS)
880 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
882 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
883 have_secondary = true;
884 if(ps_primary == ps_secondary)
885 have_secondary = true;
886 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
888 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
892 strfree(sbt_field_title[sbt_num_fields]);
893 for(i = sbt_num_fields; i > 0; --i)
895 sbt_field_title[i] = sbt_field_title[i-1];
896 sbt_field_size[i] = sbt_field_size[i-1];
897 sbt_field[i] = sbt_field[i-1];
899 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
900 sbt_field[0] = SP_NAME;
902 LOG_INFO("fixed missing field 'name'");
906 strfree(sbt_field_title[sbt_num_fields]);
907 for(i = sbt_num_fields; i > 1; --i)
909 sbt_field_title[i] = sbt_field_title[i-1];
910 sbt_field_size[i] = sbt_field_size[i-1];
911 sbt_field[i] = sbt_field[i-1];
913 sbt_field_title[1] = strzone("|");
914 sbt_field[1] = SP_SEPARATOR;
915 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
917 LOG_INFO("fixed missing field '|'");
920 else if(!have_separator)
922 strcpy(sbt_field_title[sbt_num_fields], "|");
923 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
924 sbt_field[sbt_num_fields] = SP_SEPARATOR;
926 LOG_INFO("fixed missing field '|'");
930 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
931 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
932 sbt_field[sbt_num_fields] = ps_secondary;
934 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
938 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
939 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
940 sbt_field[sbt_num_fields] = ps_primary;
942 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
946 sbt_field[sbt_num_fields] = SP_END;
949 string Scoreboard_AddPlayerId(string pl_name, entity pl)
951 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
952 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
953 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
957 vector sbt_field_rgb;
958 string sbt_field_icon0;
959 string sbt_field_icon1;
960 string sbt_field_icon2;
961 vector sbt_field_icon0_rgb;
962 vector sbt_field_icon1_rgb;
963 vector sbt_field_icon2_rgb;
964 string Scoreboard_GetName(entity pl)
966 if(ready_waiting && pl.ready)
968 sbt_field_icon0 = "gfx/scoreboard/player_ready";
972 int f = entcs_GetClientColors(pl.sv_entnum);
974 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
975 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
976 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
977 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
978 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
981 return entcs_GetName(pl.sv_entnum);
984 string Scoreboard_GetField(entity pl, PlayerScoreField field)
986 float tmp, num, denom;
989 sbt_field_rgb = '1 1 1';
990 sbt_field_icon0 = "";
991 sbt_field_icon1 = "";
992 sbt_field_icon2 = "";
993 sbt_field_icon0_rgb = '1 1 1';
994 sbt_field_icon1_rgb = '1 1 1';
995 sbt_field_icon2_rgb = '1 1 1';
1000 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1001 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1005 tmp = max(0, min(220, f-80)) / 220;
1006 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1012 f = pl.ping_packetloss;
1013 tmp = pl.ping_movementloss;
1014 if(f == 0 && tmp == 0)
1016 str = ftos(ceil(f * 100));
1018 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1019 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1020 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1024 str = Scoreboard_GetName(pl);
1025 if (autocvar_hud_panel_scoreboard_playerid)
1026 str = Scoreboard_AddPlayerId(str, pl);
1030 f = pl.(scores(SP_KILLS));
1031 f -= pl.(scores(SP_SUICIDES));
1035 num = pl.(scores(SP_KILLS));
1036 denom = pl.(scores(SP_DEATHS));
1039 sbt_field_rgb = '0 1 0';
1040 str = sprintf("%d", num);
1041 } else if(num <= 0) {
1042 sbt_field_rgb = '1 0 0';
1043 str = sprintf("%.1f", num/denom);
1045 str = sprintf("%.1f", num/denom);
1049 f = pl.(scores(SP_KILLS));
1050 f -= pl.(scores(SP_DEATHS));
1053 sbt_field_rgb = '0 1 0';
1055 sbt_field_rgb = '1 1 1';
1057 sbt_field_rgb = '1 0 0';
1063 float elo = pl.(scores(SP_ELO));
1065 case -1: return "...";
1066 case -2: return _("N/A");
1067 default: return ftos(elo);
1073 float fps = pl.(scores(SP_FPS));
1076 sbt_field_rgb = '1 1 1';
1077 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1079 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1080 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1084 case SP_DMG: case SP_DMGTAKEN:
1085 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1087 default: case SP_SCORE:
1088 tmp = pl.(scores(field));
1089 f = scores_flags(field);
1090 if(field == ps_primary)
1091 sbt_field_rgb = '1 1 0';
1092 else if(field == ps_secondary)
1093 sbt_field_rgb = '0 1 1';
1095 sbt_field_rgb = '1 1 1';
1096 return ScoreString(f, tmp);
1101 float sbt_fixcolumnwidth_len;
1102 float sbt_fixcolumnwidth_iconlen;
1103 float sbt_fixcolumnwidth_marginlen;
1105 string Scoreboard_FixColumnWidth(int i, string str)
1111 sbt_fixcolumnwidth_iconlen = 0;
1113 if(sbt_field_icon0 != "")
1115 sz = draw_getimagesize(sbt_field_icon0);
1117 if(sbt_fixcolumnwidth_iconlen < f)
1118 sbt_fixcolumnwidth_iconlen = f;
1121 if(sbt_field_icon1 != "")
1123 sz = draw_getimagesize(sbt_field_icon1);
1125 if(sbt_fixcolumnwidth_iconlen < f)
1126 sbt_fixcolumnwidth_iconlen = f;
1129 if(sbt_field_icon2 != "")
1131 sz = draw_getimagesize(sbt_field_icon2);
1133 if(sbt_fixcolumnwidth_iconlen < f)
1134 sbt_fixcolumnwidth_iconlen = f;
1137 if(sbt_fixcolumnwidth_iconlen != 0)
1139 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1140 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1143 sbt_fixcolumnwidth_marginlen = 0;
1145 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1148 float remaining_space = 0;
1149 for(j = 0; j < sbt_num_fields; ++j)
1151 if (sbt_field[i] != SP_SEPARATOR)
1152 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1153 sbt_field_size[i] = panel_size.x - remaining_space;
1155 if (sbt_fixcolumnwidth_iconlen != 0)
1156 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1157 float namesize = panel_size.x - remaining_space;
1158 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1159 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1161 max_namesize = vid_conwidth - remaining_space;
1164 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1166 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1167 if(sbt_field_size[i] < f)
1168 sbt_field_size[i] = f;
1173 void Scoreboard_initFieldSizes()
1175 for(int i = 0; i < sbt_num_fields; ++i)
1177 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1178 Scoreboard_FixColumnWidth(i, "");
1182 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1185 vector column_dim = eY * panel_size.y;
1187 column_dim.y -= 1.25 * hud_fontsize.y;
1188 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1189 pos.x += hud_fontsize.x * 0.5;
1190 for(i = 0; i < sbt_num_fields; ++i)
1192 if(sbt_field[i] == SP_SEPARATOR)
1194 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1197 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1198 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1199 pos.x += column_dim.x;
1201 if(sbt_field[i] == SP_SEPARATOR)
1203 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1204 for(i = sbt_num_fields - 1; i > 0; --i)
1206 if(sbt_field[i] == SP_SEPARATOR)
1209 pos.x -= sbt_field_size[i];
1214 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1215 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1218 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1219 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1220 pos.x -= hud_fontsize.x;
1224 pos.x = panel_pos.x;
1225 pos.y += 1.25 * hud_fontsize.y;
1229 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1231 TC(bool, is_self); TC(int, pl_number);
1233 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1235 vector h_pos = item_pos;
1236 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1237 // alternated rows highlighting
1238 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1240 if (pl == scoreboard_selected_player)
1241 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1244 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1245 else if((sbt_highlight) && (!(pl_number % 2)))
1246 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1248 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1250 vector pos = item_pos;
1251 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1253 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1255 pos.x += hud_fontsize.x * 0.5;
1256 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1257 vector tmp = '0 0 0';
1259 PlayerScoreField field;
1260 for(i = 0; i < sbt_num_fields; ++i)
1262 field = sbt_field[i];
1263 if(field == SP_SEPARATOR)
1266 if(is_spec && field != SP_NAME && field != SP_PING) {
1267 pos.x += sbt_field_size[i] + hud_fontsize.x;
1270 str = Scoreboard_GetField(pl, field);
1271 str = Scoreboard_FixColumnWidth(i, str);
1273 pos.x += sbt_field_size[i] + hud_fontsize.x;
1275 if(field == SP_NAME) {
1276 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1277 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1279 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1280 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1283 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1284 if(sbt_field_icon0 != "")
1285 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1286 if(sbt_field_icon1 != "")
1287 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1288 if(sbt_field_icon2 != "")
1289 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1292 if(sbt_field[i] == SP_SEPARATOR)
1294 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1295 for(i = sbt_num_fields-1; i > 0; --i)
1297 field = sbt_field[i];
1298 if(field == SP_SEPARATOR)
1301 if(is_spec && field != SP_NAME && field != SP_PING) {
1302 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1306 str = Scoreboard_GetField(pl, field);
1307 str = Scoreboard_FixColumnWidth(i, str);
1309 if(field == SP_NAME) {
1310 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1311 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1313 tmp.x = sbt_fixcolumnwidth_len;
1314 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1317 tmp.x = sbt_field_size[i];
1318 if(sbt_field_icon0 != "")
1319 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1320 if(sbt_field_icon1 != "")
1321 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1322 if(sbt_field_icon2 != "")
1323 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1324 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1329 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1332 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1335 vector h_pos = item_pos;
1336 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1338 bool complete = (this_team == NUM_SPECTATOR);
1341 if((sbt_highlight) && (!(pl_number % 2)))
1342 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1344 vector pos = item_pos;
1345 pos.x += hud_fontsize.x * 0.5;
1346 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1348 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1350 width_limit -= stringwidth("...", false, hud_fontsize);
1351 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1352 static float max_name_width = 0;
1354 float fieldsize = 0;
1355 float min_fieldsize = 0;
1356 float fieldpadding = hud_fontsize.x * 0.25;
1357 if(this_team == NUM_SPECTATOR)
1359 if(autocvar_hud_panel_scoreboard_spectators_showping)
1360 min_fieldsize = stringwidth("999", false, hud_fontsize);
1362 else if(autocvar_hud_panel_scoreboard_others_showscore)
1363 min_fieldsize = stringwidth("99", false, hud_fontsize);
1364 for(i = 0; pl; pl = pl.sort_next)
1366 if(pl.team != this_team)
1368 if(pl == ignored_pl)
1372 if(this_team == NUM_SPECTATOR)
1374 if(autocvar_hud_panel_scoreboard_spectators_showping)
1375 field = Scoreboard_GetField(pl, SP_PING);
1377 else if(autocvar_hud_panel_scoreboard_others_showscore)
1378 field = Scoreboard_GetField(pl, SP_SCORE);
1380 string str = entcs_GetName(pl.sv_entnum);
1381 if (autocvar_hud_panel_scoreboard_playerid)
1382 str = Scoreboard_AddPlayerId(str, pl);
1383 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1384 float column_width = stringwidth(str, true, hud_fontsize);
1385 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1387 if(column_width > max_name_width)
1388 max_name_width = column_width;
1389 column_width = max_name_width;
1393 fieldsize = stringwidth(field, false, hud_fontsize);
1394 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1397 if(pos.x + column_width > width_limit)
1402 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1407 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1408 pos.y += hud_fontsize.y * 1.25;
1412 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1414 if (pl == scoreboard_selected_player)
1416 h_size.x = column_width + hud_fontsize.x * 0.25;
1417 h_size.y = hud_fontsize.y;
1418 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1422 vector name_pos = pos;
1423 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1424 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1425 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1428 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1429 h_size.y = hud_fontsize.y;
1430 vector field_pos = pos;
1431 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1432 field_pos.x += column_width - h_size.x;
1434 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1435 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1436 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1440 h_size.x = column_width + hud_fontsize.x * 0.25;
1441 h_size.y = hud_fontsize.y;
1442 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1444 pos.x += column_width;
1445 pos.x += hud_fontsize.x;
1447 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1450 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1452 int max_players = 999;
1453 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1455 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1458 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1459 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1460 height /= team_count;
1463 height -= panel_bg_padding * 2; // - padding
1464 max_players = floor(height / (hud_fontsize.y * 1.25));
1465 if(max_players <= 1)
1467 if(max_players == tm.team_size)
1472 entity me = playerslots[current_player];
1474 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1475 panel_size.y += panel_bg_padding * 2;
1477 vector scoreboard_selected_hl_pos = pos;
1478 vector scoreboard_selected_hl_size = '0 0 0';
1479 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1480 scoreboard_selected_hl_size.y = panel_size.y;
1484 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1485 if(panel.current_panel_bg != "0")
1486 end_pos.y += panel_bg_border * 2;
1488 if(panel_bg_padding)
1490 panel_pos += '1 1 0' * panel_bg_padding;
1491 panel_size -= '2 2 0' * panel_bg_padding;
1495 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1499 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1501 pos.y += 1.25 * hud_fontsize.y;
1504 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1506 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1509 // print header row and highlight columns
1510 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1512 // fill the table and draw the rows
1513 bool is_self = false;
1514 bool self_shown = false;
1516 for(pl = players.sort_next; pl; pl = pl.sort_next)
1518 if(pl.team != tm.team)
1520 if(i == max_players - 2 && pl != me)
1522 if(!self_shown && me.team == tm.team)
1524 Scoreboard_DrawItem(pos, rgb, me, true, i);
1526 pos.y += 1.25 * hud_fontsize.y;
1530 if(i >= max_players - 1)
1532 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1535 is_self = (pl.sv_entnum == current_player);
1536 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1539 pos.y += 1.25 * hud_fontsize.y;
1543 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1545 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1547 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1548 _alpha *= panel_fg_alpha;
1550 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1554 panel_size.x += panel_bg_padding * 2; // restore initial width
1558 bool Scoreboard_WouldDraw()
1560 if (scoreboard_ui_enabled)
1562 if (scoreboard_ui_disabling)
1564 if (scoreboard_fade_alpha == 0)
1565 HUD_Scoreboard_UI_Disable_Instantly();
1568 if (intermission && scoreboard_ui_enabled == 2)
1570 HUD_Scoreboard_UI_Disable_Instantly();
1575 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1577 else if (QuickMenu_IsOpened())
1579 else if (HUD_Radar_Clickable())
1581 else if (scoreboard_showscores)
1583 else if (intermission == 1)
1585 else if (intermission == 2)
1587 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1588 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1592 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1597 float average_accuracy;
1598 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1600 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1602 WepSet weapons_stat = WepSet_GetFromStat();
1603 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1604 int disownedcnt = 0;
1606 FOREACH(Weapons, it != WEP_Null, {
1607 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1609 WepSet set = it.m_wepset;
1610 if(it.spawnflags & WEP_TYPE_OTHER)
1615 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1617 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1624 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1625 if (weapon_cnt <= 0) return pos;
1628 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1630 int columns = ceil(weapon_cnt / rows);
1632 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1633 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1634 float height = weapon_height + hud_fontsize.y;
1636 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);
1637 pos.y += 1.25 * hud_fontsize.y;
1638 if(panel.current_panel_bg != "0")
1639 pos.y += panel_bg_border;
1642 panel_size.y = height * rows;
1643 panel_size.y += panel_bg_padding * 2;
1645 float panel_bg_alpha_save = panel_bg_alpha;
1646 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1648 panel_bg_alpha = panel_bg_alpha_save;
1650 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1651 if(panel.current_panel_bg != "0")
1652 end_pos.y += panel_bg_border * 2;
1654 if(panel_bg_padding)
1656 panel_pos += '1 1 0' * panel_bg_padding;
1657 panel_size -= '2 2 0' * panel_bg_padding;
1661 vector tmp = panel_size;
1663 float weapon_width = tmp.x / columns / rows;
1666 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1670 // column highlighting
1671 for (int i = 0; i < columns; ++i)
1673 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);
1676 for (int i = 0; i < rows; ++i)
1677 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1680 average_accuracy = 0;
1681 int weapons_with_stats = 0;
1683 pos.x += weapon_width / 2;
1685 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1688 Accuracy_LoadColors();
1690 float oldposx = pos.x;
1694 FOREACH(Weapons, it != WEP_Null, {
1695 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1697 WepSet set = it.m_wepset;
1698 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1700 if (it.spawnflags & WEP_TYPE_OTHER)
1704 if (weapon_stats >= 0)
1705 weapon_alpha = sbt_fg_alpha;
1707 weapon_alpha = 0.2 * sbt_fg_alpha;
1710 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1712 if (weapon_stats >= 0) {
1713 weapons_with_stats += 1;
1714 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1716 string s = sprintf("%d%%", weapon_stats * 100);
1717 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1719 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1720 rgb = Accuracy_GetColor(weapon_stats);
1722 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1724 tmpos.x += weapon_width * rows;
1725 pos.x += weapon_width * rows;
1726 if (rows == 2 && column == columns - 1) {
1734 if (weapons_with_stats)
1735 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1737 panel_size.x += panel_bg_padding * 2; // restore initial width
1742 bool is_item_filtered(entity it)
1744 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1746 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1749 if (it.instanceOfArmor || it.instanceOfHealth)
1751 int ha_mask = floor(mask) % 10;
1754 default: return false;
1755 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1756 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1757 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1758 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1761 if (it.instanceOfAmmo)
1763 int ammo_mask = floor(mask / 10) % 10;
1764 return (ammo_mask == 1);
1769 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1771 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1773 int disowned_cnt = 0;
1774 int uninteresting_cnt = 0;
1775 IL_EACH(default_order_items, true, {
1776 int q = g_inventory.inv_items[it.m_id];
1777 //q = 1; // debug: display all items
1778 if (is_item_filtered(it))
1779 ++uninteresting_cnt;
1783 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1784 int n = items_cnt - disowned_cnt;
1785 if (n <= 0) return pos;
1787 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1788 int columns = max(6, ceil(n / rows));
1790 float item_height = hud_fontsize.y * 2.3;
1791 float height = item_height + hud_fontsize.y;
1793 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1794 pos.y += 1.25 * hud_fontsize.y;
1795 if(panel.current_panel_bg != "0")
1796 pos.y += panel_bg_border;
1799 panel_size.y = height * rows;
1800 panel_size.y += panel_bg_padding * 2;
1802 float panel_bg_alpha_save = panel_bg_alpha;
1803 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1805 panel_bg_alpha = panel_bg_alpha_save;
1807 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1808 if(panel.current_panel_bg != "0")
1809 end_pos.y += panel_bg_border * 2;
1811 if(panel_bg_padding)
1813 panel_pos += '1 1 0' * panel_bg_padding;
1814 panel_size -= '2 2 0' * panel_bg_padding;
1818 vector tmp = panel_size;
1820 float item_width = tmp.x / columns / rows;
1823 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1827 // column highlighting
1828 for (int i = 0; i < columns; ++i)
1830 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);
1833 for (int i = 0; i < rows; ++i)
1834 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1838 pos.x += item_width / 2;
1840 float oldposx = pos.x;
1844 IL_EACH(default_order_items, !is_item_filtered(it), {
1845 int n = g_inventory.inv_items[it.m_id];
1846 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1847 if (n <= 0) continue;
1848 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);
1850 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1851 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1852 tmpos.x += item_width * rows;
1853 pos.x += item_width * rows;
1854 if (rows == 2 && column == columns - 1) {
1862 panel_size.x += panel_bg_padding * 2; // restore initial width
1867 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1869 pos.x += hud_fontsize.x * 0.25;
1870 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1871 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1872 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1874 pos.y += hud_fontsize.y;
1879 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1880 float stat_secrets_found, stat_secrets_total;
1881 float stat_monsters_killed, stat_monsters_total;
1885 // get monster stats
1886 stat_monsters_killed = STAT(MONSTERS_KILLED);
1887 stat_monsters_total = STAT(MONSTERS_TOTAL);
1889 // get secrets stats
1890 stat_secrets_found = STAT(SECRETS_FOUND);
1891 stat_secrets_total = STAT(SECRETS_TOTAL);
1893 // get number of rows
1894 if(stat_secrets_total)
1896 if(stat_monsters_total)
1899 // if no rows, return
1903 // draw table header
1904 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1905 pos.y += 1.25 * hud_fontsize.y;
1906 if(panel.current_panel_bg != "0")
1907 pos.y += panel_bg_border;
1910 panel_size.y = hud_fontsize.y * rows;
1911 panel_size.y += panel_bg_padding * 2;
1914 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1915 if(panel.current_panel_bg != "0")
1916 end_pos.y += panel_bg_border * 2;
1918 if(panel_bg_padding)
1920 panel_pos += '1 1 0' * panel_bg_padding;
1921 panel_size -= '2 2 0' * panel_bg_padding;
1925 vector tmp = panel_size;
1928 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1931 if(stat_monsters_total)
1933 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1934 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1938 if(stat_secrets_total)
1940 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1941 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1944 panel_size.x += panel_bg_padding * 2; // restore initial width
1948 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1951 RANKINGS_RECEIVED_CNT = 0;
1952 for (i=RANKINGS_CNT-1; i>=0; --i)
1954 ++RANKINGS_RECEIVED_CNT;
1956 if (RANKINGS_RECEIVED_CNT == 0)
1959 vector hl_rgb = rgb + '0.5 0.5 0.5';
1961 vector scoreboard_selected_hl_pos = pos;
1963 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1964 pos.y += 1.25 * hud_fontsize.y;
1965 if(panel.current_panel_bg != "0")
1966 pos.y += panel_bg_border;
1968 vector scoreboard_selected_hl_size = '0 0 0';
1969 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1970 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1975 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1977 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1982 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1984 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1988 float ranksize = 3 * hud_fontsize.x;
1989 float timesize = 5 * hud_fontsize.x;
1990 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1991 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1992 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1995 rankings_cnt = RANKINGS_RECEIVED_CNT;
1996 rankings_rows = ceil(rankings_cnt / rankings_columns);
1999 // expand name column to fill the entire row
2000 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2001 namesize += available_space;
2002 columnsize.x += available_space;
2004 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2005 panel_size.y += panel_bg_padding * 2;
2006 scoreboard_selected_hl_size.y += panel_size.y;
2010 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2011 if(panel.current_panel_bg != "0")
2012 end_pos.y += panel_bg_border * 2;
2014 if(panel_bg_padding)
2016 panel_pos += '1 1 0' * panel_bg_padding;
2017 panel_size -= '2 2 0' * panel_bg_padding;
2023 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2025 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2027 int column = 0, j = 0;
2028 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2029 int start_item = rankings_start_column * rankings_rows;
2030 for(i = start_item; i < start_item + rankings_cnt; ++i)
2032 int t = grecordtime[i];
2036 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2037 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2038 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2039 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2041 str = count_ordinal(i+1);
2042 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2043 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2044 str = ColorTranslateRGB(grecordholder[i]);
2046 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2047 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2049 pos.y += 1.25 * hud_fontsize.y;
2051 if(j >= rankings_rows)
2055 pos.x += panel_size.x / rankings_columns;
2056 pos.y = panel_pos.y;
2059 strfree(zoned_name_self);
2061 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2063 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2064 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2067 panel_size.x += panel_bg_padding * 2; // restore initial width
2071 bool have_weapon_stats;
2072 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2074 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2076 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2079 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2080 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2086 if (!have_weapon_stats)
2088 FOREACH(Weapons, it != WEP_Null, {
2089 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2090 if (weapon_stats >= 0)
2092 have_weapon_stats = true;
2096 if (!have_weapon_stats)
2103 bool have_item_stats;
2104 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2106 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2108 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2111 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2112 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2118 if (!have_item_stats)
2120 IL_EACH(default_order_items, true, {
2121 if (!is_item_filtered(it))
2123 int q = g_inventory.inv_items[it.m_id];
2124 //q = 1; // debug: display all items
2127 have_item_stats = true;
2132 if (!have_item_stats)
2139 vector Scoreboard_Spectators_Draw(vector pos) {
2144 for(pl = players.sort_next; pl; pl = pl.sort_next)
2146 if(pl.team == NUM_SPECTATOR)
2148 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2149 if(tm.team == NUM_SPECTATOR)
2151 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2152 draw_beginBoldFont();
2153 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2155 pos.y += 1.25 * hud_fontsize.y;
2157 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2158 pos.y += 1.25 * hud_fontsize.y;
2163 if (str != "") // if there's at least one spectator
2164 pos.y += 0.5 * hud_fontsize.y;
2169 void Scoreboard_Draw()
2171 if(!autocvar__hud_configure)
2173 if(!hud_draw_maximized) return;
2175 // frametime checks allow to toggle the scoreboard even when the game is paused
2176 if(scoreboard_active) {
2177 if (scoreboard_fade_alpha == 0)
2178 scoreboard_time = time;
2179 if(hud_configure_menu_open == 1)
2180 scoreboard_fade_alpha = 1;
2181 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2182 if (scoreboard_fadeinspeed && frametime)
2183 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2185 scoreboard_fade_alpha = 1;
2186 if(hud_fontsize_str != autocvar_hud_fontsize)
2188 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2189 Scoreboard_initFieldSizes();
2190 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2194 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2195 if (scoreboard_fadeoutspeed && frametime)
2196 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2198 scoreboard_fade_alpha = 0;
2201 if (!scoreboard_fade_alpha)
2203 scoreboard_acc_fade_alpha = 0;
2204 scoreboard_itemstats_fade_alpha = 0;
2209 scoreboard_fade_alpha = 0;
2211 if (autocvar_hud_panel_scoreboard_dynamichud)
2214 HUD_Scale_Disable();
2216 if(scoreboard_fade_alpha <= 0)
2218 panel_fade_alpha *= scoreboard_fade_alpha;
2219 HUD_Panel_LoadCvars();
2221 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2222 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2223 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2224 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2225 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2226 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2227 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2229 // don't overlap with con_notify
2230 if(!autocvar__hud_configure)
2231 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2233 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2234 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2235 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2236 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2237 panel_pos.x = scoreboard_left;
2238 panel_size.x = fixed_scoreboard_width;
2240 Scoreboard_UpdatePlayerTeams();
2242 scoreboard_top = panel_pos.y;
2243 vector pos = panel_pos;
2248 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2250 // Begin of Game Info Section
2251 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2252 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2254 // Game Info: Game Type
2255 if (scoreboard_ui_enabled == 2)
2256 str = _("Team Selection");
2258 str = MapInfo_Type_ToText(gametype);
2259 draw_beginBoldFont();
2260 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);
2263 pos.y += sb_gameinfo_type_fontsize.y;
2264 // Game Info: Game Detail
2265 if (scoreboard_ui_enabled == 2)
2267 if (scoreboard_selected_team)
2268 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2270 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2271 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);
2273 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2274 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2275 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);
2279 float tl = STAT(TIMELIMIT);
2280 float fl = STAT(FRAGLIMIT);
2281 float ll = STAT(LEADLIMIT);
2282 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2285 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2286 if(!gametype.m_hidelimits)
2291 str = strcat(str, "^7 / "); // delimiter
2294 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2295 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2296 (teamscores_label(ts_primary) == "fastest") ? "" :
2297 TranslateScoresLabel(teamscores_label(ts_primary))));
2301 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2302 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2303 (scores_label(ps_primary) == "fastest") ? "" :
2304 TranslateScoresLabel(scores_label(ps_primary))));
2309 if(tl > 0 || fl > 0)
2312 if (ll_and_fl && fl > 0)
2313 str = strcat(str, "^7 & ");
2315 str = strcat(str, "^7 / ");
2320 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2321 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2322 (teamscores_label(ts_primary) == "fastest") ? "" :
2323 TranslateScoresLabel(teamscores_label(ts_primary))));
2327 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2328 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2329 (scores_label(ps_primary) == "fastest") ? "" :
2330 TranslateScoresLabel(scores_label(ps_primary))));
2334 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
2336 str = sprintf(_("^7Map: ^2%s"), mi_shortname);
2337 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2339 // End of Game Info Section
2341 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2342 if(panel.current_panel_bg != "0")
2343 pos.y += panel_bg_border;
2345 // Draw the scoreboard
2346 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2349 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2353 vector panel_bg_color_save = panel_bg_color;
2354 vector team_score_baseoffset;
2355 vector team_size_baseoffset;
2356 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2358 // put team score to the left of scoreboard (and team size to the right)
2359 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2360 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2361 if(panel.current_panel_bg != "0")
2363 team_score_baseoffset.x -= panel_bg_border;
2364 team_size_baseoffset.x += panel_bg_border;
2369 // put team score to the right of scoreboard (and team size to the left)
2370 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2371 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2372 if(panel.current_panel_bg != "0")
2374 team_score_baseoffset.x += panel_bg_border;
2375 team_size_baseoffset.x -= panel_bg_border;
2379 int team_size_total = 0;
2380 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2382 // calculate team size total (sum of all team sizes)
2383 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2384 if(tm.team != NUM_SPECTATOR)
2385 team_size_total += tm.team_size;
2388 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2390 if(tm.team == NUM_SPECTATOR)
2395 draw_beginBoldFont();
2396 vector rgb = Team_ColorRGB(tm.team);
2397 str = ftos(tm.(teamscores(ts_primary)));
2398 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2400 // team score on the left (default)
2401 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2405 // team score on the right
2406 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2408 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2410 // team size (if set to show on the side)
2411 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2413 // calculate the starting position for the whole team size info string
2414 str = sprintf("%d/%d", tm.team_size, team_size_total);
2415 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2417 // team size on the left
2418 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2422 // team size on the right
2423 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2425 str = sprintf("%d", tm.team_size);
2426 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2427 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2428 str = sprintf("/%d", team_size_total);
2429 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2433 // secondary score, e.g. keyhunt
2434 if(ts_primary != ts_secondary)
2436 str = ftos(tm.(teamscores(ts_secondary)));
2437 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2440 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2445 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2448 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2451 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2452 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2453 else if(panel_bg_color_team > 0)
2454 panel_bg_color = rgb * panel_bg_color_team;
2456 panel_bg_color = rgb;
2457 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2459 panel_bg_color = panel_bg_color_save;
2463 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2464 if(tm.team != NUM_SPECTATOR)
2467 // display it anyway
2468 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2471 // draw scoreboard spectators before accuracy and item stats
2472 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2473 pos = Scoreboard_Spectators_Draw(pos);
2476 // draw accuracy and item stats
2477 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2478 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2479 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2480 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2482 // draw scoreboard spectators after accuracy and item stats and before rankings
2483 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2484 pos = Scoreboard_Spectators_Draw(pos);
2487 if(MUTATOR_CALLHOOK(ShowRankings)) {
2488 string ranktitle = M_ARGV(0, string);
2489 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2490 if(race_speedaward_alltimebest)
2493 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2497 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2498 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2499 str = strcat(str, " / ");
2501 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2502 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2503 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2504 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2506 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2511 // draw scoreboard spectators after rankings
2512 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2513 pos = Scoreboard_Spectators_Draw(pos);
2516 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2518 // draw scoreboard spectators after mapstats
2519 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2520 pos = Scoreboard_Spectators_Draw(pos);
2524 // print information about respawn status
2525 float respawn_time = STAT(RESPAWN_TIME);
2526 if(!intermission && respawn_time)
2528 if(respawn_time < 0)
2530 // a negative number means we are awaiting respawn, time value is still the same
2531 respawn_time *= -1; // remove mark now that we checked it
2533 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2534 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2536 str = sprintf(_("^1Respawning in ^3%s^1..."),
2537 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2538 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2540 count_seconds(ceil(respawn_time - time))
2544 else if(time < respawn_time)
2546 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2547 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2548 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2550 count_seconds(ceil(respawn_time - time))
2554 else if(time >= respawn_time)
2555 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2557 pos.y += 1.2 * hud_fontsize.y;
2558 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2561 pos.y += hud_fontsize.y;
2562 if (scoreboard_fade_alpha < 1)
2563 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2564 else if (pos.y != scoreboard_bottom)
2566 if (pos.y > scoreboard_bottom)
2567 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2569 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2574 if (scoreboard_fade_alpha == 1)
2576 if (scoreboard_bottom > 0.95 * vid_conheight)
2577 rankings_rows = max(1, rankings_rows - 1);
2578 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2579 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2581 rankings_cnt = rankings_rows * rankings_columns;