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_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
111 float scoreboard_time;
113 // mode 0: returns translated label
114 // mode 1: prints name and description of all the labels
115 string Label_getInfo(string label, int mode)
118 label = "bckills"; // first case in the switch
122 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
123 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
124 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")));
125 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
126 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
127 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
128 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
129 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
130 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
131 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
132 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
133 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
134 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
135 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
136 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
137 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
138 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
139 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
140 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
141 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
142 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
143 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
144 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
145 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
146 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
147 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
148 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
149 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")));
150 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
151 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
152 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
153 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
154 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
155 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
156 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
157 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
158 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
159 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
160 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
161 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
162 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
163 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
164 default: return label;
169 void HUD_Scoreboard_UI_Disable()
171 scoreboard_showscores = false;
172 scoreboard_ui_enabled = 0;
173 scoreboard_selected_panel = 0;
174 scoreboard_selected_player = NULL;
175 scoreboard_selected_team = NULL;
178 // mode: 0 normal, 1 team selection
179 void Scoreboard_UI_Enable(int mode)
183 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
186 // release player's pressed keys as they aren't released elsewhere
187 // in particular jump needs to be released as it may open the team selection
188 // (when server detects jump has been pressed it sends the command to open the team selection)
189 Release_Common_Keys();
190 scoreboard_ui_enabled = 2;
191 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
195 if (scoreboard_ui_enabled == 1)
197 scoreboard_ui_enabled = 1;
198 scoreboard_selected_panel = SB_PANEL_FIRST;
200 scoreboard_selected_player = NULL;
201 scoreboard_selected_team = NULL;
202 scoreboard_selected_panel_time = time;
205 int rankings_start_column;
206 int rankings_rows = 0;
207 int rankings_columns = 0;
208 int rankings_cnt = 0;
209 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
213 if(!scoreboard_ui_enabled)
218 mousepos.x = nPrimary;
219 mousepos.y = nSecondary;
226 // at this point bInputType can only be 0 or 1 (key pressed or released)
227 bool key_pressed = (bInputType == 0);
229 // ESC to exit (TAB-ESC works too)
230 if(nPrimary == K_ESCAPE)
234 HUD_Scoreboard_UI_Disable();
238 // block any input while a menu dialog is fading
239 if(autocvar__menu_alpha)
245 // allow console bind to work
246 string con_keys = findkeysforcommand("toggleconsole", 0);
247 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
249 bool hit_con_bind = false;
251 for (i = 0; i < keys; ++i)
253 if(nPrimary == stof(argv(i)))
258 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
259 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
260 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
261 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
264 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
265 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
266 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
267 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
270 if(nPrimary == K_TAB)
274 if (scoreboard_ui_enabled == 2)
276 if (hudShiftState & S_SHIFT)
279 goto downarrow_action;
282 if (hudShiftState & S_SHIFT)
284 --scoreboard_selected_panel;
285 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
286 --scoreboard_selected_panel;
287 if (scoreboard_selected_panel < SB_PANEL_FIRST)
288 scoreboard_selected_panel = SB_PANEL_MAX;
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_MAX)
296 scoreboard_selected_panel = SB_PANEL_FIRST;
299 scoreboard_selected_panel_time = time;
301 else if(nPrimary == K_DOWNARROW)
305 LABEL(downarrow_action);
306 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
308 if (scoreboard_ui_enabled == 2)
310 entity curr_team = NULL;
311 bool scoreboard_selected_team_found = false;
312 if (!scoreboard_selected_team)
313 scoreboard_selected_team_found = true;
315 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
317 if(tm.team == NUM_SPECTATOR)
320 if (scoreboard_selected_team_found)
322 if (scoreboard_selected_team == tm)
323 scoreboard_selected_team_found = true;
326 if (curr_team == scoreboard_selected_team) // loop reached the last team
328 scoreboard_selected_team = curr_team;
333 entity curr_pl = NULL;
334 bool scoreboard_selected_player_found = false;
335 if (!scoreboard_selected_player)
336 scoreboard_selected_player_found = true;
338 for(tm = teams.sort_next; tm; tm = tm.sort_next)
340 if(tm.team != NUM_SPECTATOR)
341 for(pl = players.sort_next; pl; pl = pl.sort_next)
343 if(pl.team != tm.team)
346 if (scoreboard_selected_player_found)
348 if (scoreboard_selected_player == pl)
349 scoreboard_selected_player_found = true;
353 if (curr_pl == scoreboard_selected_player) // loop reached the last player
355 scoreboard_selected_player = curr_pl;
359 else if(nPrimary == K_UPARROW)
363 LABEL(uparrow_action);
364 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
366 if (scoreboard_ui_enabled == 2)
368 entity prev_team = NULL;
369 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
371 if(tm.team == NUM_SPECTATOR)
373 if (tm == scoreboard_selected_team)
378 scoreboard_selected_team = prev_team;
382 entity prev_pl = NULL;
384 for(tm = teams.sort_next; tm; tm = tm.sort_next)
386 if(tm.team != NUM_SPECTATOR)
387 for(pl = players.sort_next; pl; pl = pl.sort_next)
389 if(pl.team != tm.team)
391 if (pl == scoreboard_selected_player)
397 scoreboard_selected_player = prev_pl;
401 else if(nPrimary == K_RIGHTARROW)
405 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
406 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
408 else if(nPrimary == K_LEFTARROW)
412 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
413 rankings_start_column = max(rankings_start_column - 1, 0);
415 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
419 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
421 if (scoreboard_ui_enabled == 2)
424 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
427 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
428 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
429 HUD_Scoreboard_UI_Disable();
431 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
434 HUD_Scoreboard_UI_Disable();
437 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
440 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
444 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
446 switch (scoreboard_selected_columns_layout)
449 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
451 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
452 scoreboard_selected_columns_layout = 1;
457 localcmd(sprintf("scoreboard_columns_set default\n"));
458 scoreboard_selected_columns_layout = 2;
461 localcmd(sprintf("scoreboard_columns_set all\n"));
462 scoreboard_selected_columns_layout = 0;
467 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
471 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
473 if (scoreboard_selected_player)
475 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
476 HUD_Scoreboard_UI_Disable();
480 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
484 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
486 if (scoreboard_selected_player)
487 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
490 else if(hit_con_bind || nPrimary == K_PAUSE)
496 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
497 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
499 #define SB_EXTRA_SORTING_FIELDS 5
500 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
501 void Scoreboard_InitScores()
505 ps_primary = ps_secondary = NULL;
506 ts_primary = ts_secondary = -1;
507 FOREACH(Scores, true, {
508 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
509 if(f == SFL_SORT_PRIO_PRIMARY)
511 if(f == SFL_SORT_PRIO_SECONDARY)
513 if(ps_primary == it || ps_secondary == it)
515 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
516 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
517 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
518 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
519 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
521 if(ps_secondary == NULL)
522 ps_secondary = ps_primary;
524 for(i = 0; i < MAX_TEAMSCORE; ++i)
526 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
527 if(f == SFL_SORT_PRIO_PRIMARY)
529 if(f == SFL_SORT_PRIO_SECONDARY)
532 if(ts_secondary == -1)
533 ts_secondary = ts_primary;
535 Cmd_Scoreboard_SetFields(0);
539 void Scoreboard_UpdatePlayerTeams()
541 static float update_time;
542 if (time <= update_time)
548 for(pl = players.sort_next; pl; pl = pl.sort_next)
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)
607 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
611 if (!fld) fld = ps_primary;
612 else if (ps_secondary == ps_primary) continue;
613 else fld = ps_secondary;
617 fld = sb_extra_sorting_field[i];
618 if (fld == ps_primary || fld == ps_secondary) continue;
622 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
623 if (r >= 0) return r;
626 if (left.sv_entnum < right.sv_entnum)
632 void Scoreboard_UpdatePlayerPos(entity player)
635 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
637 SORT_SWAP(player, ent);
639 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
641 SORT_SWAP(ent, player);
645 float Scoreboard_CompareTeamScores(entity left, entity right)
647 if(left.team == NUM_SPECTATOR)
649 if(right.team == NUM_SPECTATOR)
654 for(int i = -2; i < MAX_TEAMSCORE; ++i)
658 if (fld_idx == -1) fld_idx = ts_primary;
659 else if (ts_secondary == ts_primary) continue;
660 else fld_idx = ts_secondary;
665 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
668 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
669 if (r >= 0) return r;
672 if (left.team < right.team)
678 void Scoreboard_UpdateTeamPos(entity Team)
681 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
683 SORT_SWAP(Team, ent);
685 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
687 SORT_SWAP(ent, Team);
691 void Cmd_Scoreboard_Help()
693 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
694 LOG_HELP(_("Usage:"));
695 LOG_HELP("^2scoreboard_columns_set ^3default");
696 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
697 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
698 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
699 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
700 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
701 LOG_HELP(_("The following field names are recognized (case insensitive):"));
707 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
708 "of game types, then a slash, to make the field show up only in these\n"
709 "or in all but these game types. You can also specify 'all' as a\n"
710 "field to show all fields available for the current game mode."));
713 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
714 "include/exclude ALL teams/noteams game modes."));
717 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
718 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
719 "right of the vertical bar aligned to the right."));
720 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
721 "other gamemodes except DM."));
724 // NOTE: adding a gametype with ? to not warn for an optional field
725 // make sure it's excluded in a previous exclusive rule, if any
726 // otherwise the previous exclusive rule warns anyway
727 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
728 #define SCOREBOARD_DEFAULT_COLUMNS \
729 "ping pl fps name |" \
730 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
731 " -teams,lms/deaths +ft,tdm/deaths" \
733 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
734 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
735 " +tdm,ft,dom,ons,as/teamkills"\
736 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
737 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
738 " +lms/lives +lms/rank" \
739 " +kh/kckills +kh/losses +kh/caps" \
740 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
741 " +as/objectives +nb/faults +nb/goals" \
742 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
743 " +dom/ticks +dom/takes" \
744 " -lms,rc,cts,inv,nb/score"
746 void Cmd_Scoreboard_SetFields(int argc)
751 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
755 return; // do nothing, we don't know gametype and scores yet
757 // sbt_fields uses strunzone on the titles!
758 if(!sbt_field_title[0])
759 for(i = 0; i < MAX_SBT_FIELDS; ++i)
760 sbt_field_title[i] = strzone("(null)");
762 // TODO: re enable with gametype dependant cvars?
763 if(argc < 3) // no arguments provided
764 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
767 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
771 if(argv(2) == "default" || argv(2) == "expand_default")
773 if(argv(2) == "expand_default")
774 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
775 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
777 else if(argv(2) == "all" || argv(2) == "ALL")
779 string s = "ping pl name |"; // scores without label (not really scores)
782 // scores without label
783 s = strcat(s, " ", "sum");
784 s = strcat(s, " ", "kdratio");
785 s = strcat(s, " ", "frags");
787 FOREACH(Scores, true, {
789 if(it != ps_secondary)
790 if(scores_label(it) != "")
791 s = strcat(s, " ", scores_label(it));
793 if(ps_secondary != ps_primary)
794 s = strcat(s, " ", scores_label(ps_secondary));
795 s = strcat(s, " ", scores_label(ps_primary));
796 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
803 hud_fontsize = HUD_GetFontsize("hud_fontsize");
805 for(i = 1; i < argc - 1; ++i)
808 bool nocomplain = false;
809 if(substring(str, 0, 1) == "?")
812 str = substring(str, 1, strlen(str) - 1);
815 slash = strstrofs(str, "/", 0);
818 pattern = substring(str, 0, slash);
819 str = substring(str, slash + 1, strlen(str) - (slash + 1));
821 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
825 str = strtolower(str);
826 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
827 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
832 // fields without a label (not networked via the score system)
833 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
834 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
835 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
836 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
837 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
838 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
839 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
840 default: // fields with a label
842 // map alternative labels
843 if (str == "damage") str = "dmg";
844 if (str == "damagetaken") str = "dmgtaken";
846 FOREACH(Scores, true, {
847 if (str == strtolower(scores_label(it))) {
849 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
853 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
854 if(!nocomplain && str != "fps") // server can disable the fps field
855 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
857 strfree(sbt_field_title[sbt_num_fields]);
858 sbt_field_size[sbt_num_fields] = 0;
862 sbt_field[sbt_num_fields] = j;
865 if(j == ps_secondary)
866 have_secondary = true;
871 if(sbt_num_fields >= MAX_SBT_FIELDS)
875 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
877 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
878 have_secondary = true;
879 if(ps_primary == ps_secondary)
880 have_secondary = true;
881 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
883 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
887 strfree(sbt_field_title[sbt_num_fields]);
888 for(i = sbt_num_fields; i > 0; --i)
890 sbt_field_title[i] = sbt_field_title[i-1];
891 sbt_field_size[i] = sbt_field_size[i-1];
892 sbt_field[i] = sbt_field[i-1];
894 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
895 sbt_field[0] = SP_NAME;
897 LOG_INFO("fixed missing field 'name'");
901 strfree(sbt_field_title[sbt_num_fields]);
902 for(i = sbt_num_fields; i > 1; --i)
904 sbt_field_title[i] = sbt_field_title[i-1];
905 sbt_field_size[i] = sbt_field_size[i-1];
906 sbt_field[i] = sbt_field[i-1];
908 sbt_field_title[1] = strzone("|");
909 sbt_field[1] = SP_SEPARATOR;
910 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
912 LOG_INFO("fixed missing field '|'");
915 else if(!have_separator)
917 strcpy(sbt_field_title[sbt_num_fields], "|");
918 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
919 sbt_field[sbt_num_fields] = SP_SEPARATOR;
921 LOG_INFO("fixed missing field '|'");
925 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
926 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
927 sbt_field[sbt_num_fields] = ps_secondary;
929 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
933 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
934 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
935 sbt_field[sbt_num_fields] = ps_primary;
937 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
941 sbt_field[sbt_num_fields] = SP_END;
944 string Scoreboard_AddPlayerId(string pl_name, entity pl)
946 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
947 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
948 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
952 vector sbt_field_rgb;
953 string sbt_field_icon0;
954 string sbt_field_icon1;
955 string sbt_field_icon2;
956 vector sbt_field_icon0_rgb;
957 vector sbt_field_icon1_rgb;
958 vector sbt_field_icon2_rgb;
959 string Scoreboard_GetName(entity pl)
961 if(ready_waiting && pl.ready)
963 sbt_field_icon0 = "gfx/scoreboard/player_ready";
967 int f = entcs_GetClientColors(pl.sv_entnum);
969 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
970 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
971 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
972 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
973 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
976 return entcs_GetName(pl.sv_entnum);
979 string Scoreboard_GetField(entity pl, PlayerScoreField field)
981 float tmp, num, denom;
984 sbt_field_rgb = '1 1 1';
985 sbt_field_icon0 = "";
986 sbt_field_icon1 = "";
987 sbt_field_icon2 = "";
988 sbt_field_icon0_rgb = '1 1 1';
989 sbt_field_icon1_rgb = '1 1 1';
990 sbt_field_icon2_rgb = '1 1 1';
995 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
996 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1000 tmp = max(0, min(220, f-80)) / 220;
1001 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1007 f = pl.ping_packetloss;
1008 tmp = pl.ping_movementloss;
1009 if(f == 0 && tmp == 0)
1011 str = ftos(ceil(f * 100));
1013 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1014 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1015 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1019 str = Scoreboard_GetName(pl);
1020 if (autocvar_hud_panel_scoreboard_playerid)
1021 str = Scoreboard_AddPlayerId(str, pl);
1025 f = pl.(scores(SP_KILLS));
1026 f -= pl.(scores(SP_SUICIDES));
1030 num = pl.(scores(SP_KILLS));
1031 denom = pl.(scores(SP_DEATHS));
1034 sbt_field_rgb = '0 1 0';
1035 str = sprintf("%d", num);
1036 } else if(num <= 0) {
1037 sbt_field_rgb = '1 0 0';
1038 str = sprintf("%.1f", num/denom);
1040 str = sprintf("%.1f", num/denom);
1044 f = pl.(scores(SP_KILLS));
1045 f -= pl.(scores(SP_DEATHS));
1048 sbt_field_rgb = '0 1 0';
1050 sbt_field_rgb = '1 1 1';
1052 sbt_field_rgb = '1 0 0';
1058 float elo = pl.(scores(SP_ELO));
1060 case -1: return "...";
1061 case -2: return _("N/A");
1062 default: return ftos(elo);
1068 float fps = pl.(scores(SP_FPS));
1071 sbt_field_rgb = '1 1 1';
1072 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1074 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1075 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1079 case SP_DMG: case SP_DMGTAKEN:
1080 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1082 default: case SP_SCORE:
1083 tmp = pl.(scores(field));
1084 f = scores_flags(field);
1085 if(field == ps_primary)
1086 sbt_field_rgb = '1 1 0';
1087 else if(field == ps_secondary)
1088 sbt_field_rgb = '0 1 1';
1090 sbt_field_rgb = '1 1 1';
1091 return ScoreString(f, tmp);
1096 float sbt_fixcolumnwidth_len;
1097 float sbt_fixcolumnwidth_iconlen;
1098 float sbt_fixcolumnwidth_marginlen;
1100 string Scoreboard_FixColumnWidth(int i, string str)
1106 sbt_fixcolumnwidth_iconlen = 0;
1108 if(sbt_field_icon0 != "")
1110 sz = draw_getimagesize(sbt_field_icon0);
1112 if(sbt_fixcolumnwidth_iconlen < f)
1113 sbt_fixcolumnwidth_iconlen = f;
1116 if(sbt_field_icon1 != "")
1118 sz = draw_getimagesize(sbt_field_icon1);
1120 if(sbt_fixcolumnwidth_iconlen < f)
1121 sbt_fixcolumnwidth_iconlen = f;
1124 if(sbt_field_icon2 != "")
1126 sz = draw_getimagesize(sbt_field_icon2);
1128 if(sbt_fixcolumnwidth_iconlen < f)
1129 sbt_fixcolumnwidth_iconlen = f;
1132 if(sbt_fixcolumnwidth_iconlen != 0)
1134 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1135 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1138 sbt_fixcolumnwidth_marginlen = 0;
1140 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1143 float remaining_space = 0;
1144 for(j = 0; j < sbt_num_fields; ++j)
1146 if (sbt_field[i] != SP_SEPARATOR)
1147 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1148 sbt_field_size[i] = panel_size.x - remaining_space;
1150 if (sbt_fixcolumnwidth_iconlen != 0)
1151 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1152 float namesize = panel_size.x - remaining_space;
1153 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1154 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1156 max_namesize = vid_conwidth - remaining_space;
1159 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1161 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1162 if(sbt_field_size[i] < f)
1163 sbt_field_size[i] = f;
1168 void Scoreboard_initFieldSizes()
1170 for(int i = 0; i < sbt_num_fields; ++i)
1172 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1173 Scoreboard_FixColumnWidth(i, "");
1177 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1180 vector column_dim = eY * panel_size.y;
1182 column_dim.y -= 1.25 * hud_fontsize.y;
1183 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1184 pos.x += hud_fontsize.x * 0.5;
1185 for(i = 0; i < sbt_num_fields; ++i)
1187 if(sbt_field[i] == SP_SEPARATOR)
1189 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1192 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1193 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1194 pos.x += column_dim.x;
1196 if(sbt_field[i] == SP_SEPARATOR)
1198 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1199 for(i = sbt_num_fields - 1; i > 0; --i)
1201 if(sbt_field[i] == SP_SEPARATOR)
1204 pos.x -= sbt_field_size[i];
1209 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1210 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1213 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1214 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1215 pos.x -= hud_fontsize.x;
1219 pos.x = panel_pos.x;
1220 pos.y += 1.25 * hud_fontsize.y;
1224 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1226 TC(bool, is_self); TC(int, pl_number);
1228 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1230 vector h_pos = item_pos;
1231 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1232 // alternated rows highlighting
1233 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1235 if (pl == scoreboard_selected_player)
1236 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1239 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1240 else if((sbt_highlight) && (!(pl_number % 2)))
1241 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1243 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1245 vector pos = item_pos;
1246 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1248 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1250 pos.x += hud_fontsize.x * 0.5;
1251 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1252 vector tmp = '0 0 0';
1254 PlayerScoreField field;
1255 for(i = 0; i < sbt_num_fields; ++i)
1257 field = sbt_field[i];
1258 if(field == SP_SEPARATOR)
1261 if(is_spec && field != SP_NAME && field != SP_PING) {
1262 pos.x += sbt_field_size[i] + hud_fontsize.x;
1265 str = Scoreboard_GetField(pl, field);
1266 str = Scoreboard_FixColumnWidth(i, str);
1268 pos.x += sbt_field_size[i] + hud_fontsize.x;
1270 if(field == SP_NAME) {
1271 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1272 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1274 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1275 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1278 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1279 if(sbt_field_icon0 != "")
1280 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1281 if(sbt_field_icon1 != "")
1282 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1283 if(sbt_field_icon2 != "")
1284 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1287 if(sbt_field[i] == SP_SEPARATOR)
1289 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1290 for(i = sbt_num_fields-1; i > 0; --i)
1292 field = sbt_field[i];
1293 if(field == SP_SEPARATOR)
1296 if(is_spec && field != SP_NAME && field != SP_PING) {
1297 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1301 str = Scoreboard_GetField(pl, field);
1302 str = Scoreboard_FixColumnWidth(i, str);
1304 if(field == SP_NAME) {
1305 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1306 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1308 tmp.x = sbt_fixcolumnwidth_len;
1309 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1312 tmp.x = sbt_field_size[i];
1313 if(sbt_field_icon0 != "")
1314 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1315 if(sbt_field_icon1 != "")
1316 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1317 if(sbt_field_icon2 != "")
1318 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1319 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1324 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1327 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1330 vector h_pos = item_pos;
1331 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1333 bool complete = (this_team == NUM_SPECTATOR);
1336 if((sbt_highlight) && (!(pl_number % 2)))
1337 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1339 vector pos = item_pos;
1340 pos.x += hud_fontsize.x * 0.5;
1341 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1343 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1345 width_limit -= stringwidth("...", false, hud_fontsize);
1346 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1347 static float max_name_width = 0;
1349 float fieldsize = 0;
1350 float min_fieldsize = 0;
1351 float fieldpadding = hud_fontsize.x * 0.25;
1352 if(this_team == NUM_SPECTATOR)
1354 if(autocvar_hud_panel_scoreboard_spectators_showping)
1355 min_fieldsize = stringwidth("999", false, hud_fontsize);
1357 else if(autocvar_hud_panel_scoreboard_others_showscore)
1358 min_fieldsize = stringwidth("99", false, hud_fontsize);
1359 for(i = 0; pl; pl = pl.sort_next)
1361 if(pl.team != this_team)
1363 if(pl == ignored_pl)
1367 if(this_team == NUM_SPECTATOR)
1369 if(autocvar_hud_panel_scoreboard_spectators_showping)
1370 field = Scoreboard_GetField(pl, SP_PING);
1372 else if(autocvar_hud_panel_scoreboard_others_showscore)
1373 field = Scoreboard_GetField(pl, SP_SCORE);
1375 string str = entcs_GetName(pl.sv_entnum);
1376 if (autocvar_hud_panel_scoreboard_playerid)
1377 str = Scoreboard_AddPlayerId(str, pl);
1378 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1379 float column_width = stringwidth(str, true, hud_fontsize);
1380 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1382 if(column_width > max_name_width)
1383 max_name_width = column_width;
1384 column_width = max_name_width;
1388 fieldsize = stringwidth(field, false, hud_fontsize);
1389 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1392 if(pos.x + column_width > width_limit)
1397 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1402 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1403 pos.y += hud_fontsize.y * 1.25;
1407 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1409 if (pl == scoreboard_selected_player)
1411 h_size.x = column_width + hud_fontsize.x * 0.25;
1412 h_size.y = hud_fontsize.y;
1413 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1417 vector name_pos = pos;
1418 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1419 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1420 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1423 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1424 h_size.y = hud_fontsize.y;
1425 vector field_pos = pos;
1426 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1427 field_pos.x += column_width - h_size.x;
1429 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1430 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1431 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1435 h_size.x = column_width + hud_fontsize.x * 0.25;
1436 h_size.y = hud_fontsize.y;
1437 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1439 pos.x += column_width;
1440 pos.x += hud_fontsize.x;
1442 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1445 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1447 int max_players = 999;
1448 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1450 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1453 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1454 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1455 height /= team_count;
1458 height -= panel_bg_padding * 2; // - padding
1459 max_players = floor(height / (hud_fontsize.y * 1.25));
1460 if(max_players <= 1)
1462 if(max_players == tm.team_size)
1467 entity me = playerslots[current_player];
1469 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1470 panel_size.y += panel_bg_padding * 2;
1472 vector scoreboard_selected_hl_pos = pos;
1473 vector scoreboard_selected_hl_size = '0 0 0';
1474 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1475 scoreboard_selected_hl_size.y = panel_size.y;
1479 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1480 if(panel.current_panel_bg != "0")
1481 end_pos.y += panel_bg_border * 2;
1483 if(panel_bg_padding)
1485 panel_pos += '1 1 0' * panel_bg_padding;
1486 panel_size -= '2 2 0' * panel_bg_padding;
1490 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1494 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1496 pos.y += 1.25 * hud_fontsize.y;
1499 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1501 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1504 // print header row and highlight columns
1505 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1507 // fill the table and draw the rows
1508 bool is_self = false;
1509 bool self_shown = false;
1511 for(pl = players.sort_next; pl; pl = pl.sort_next)
1513 if(pl.team != tm.team)
1515 if(i == max_players - 2 && pl != me)
1517 if(!self_shown && me.team == tm.team)
1519 Scoreboard_DrawItem(pos, rgb, me, true, i);
1521 pos.y += 1.25 * hud_fontsize.y;
1525 if(i >= max_players - 1)
1527 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1530 is_self = (pl.sv_entnum == current_player);
1531 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1534 pos.y += 1.25 * hud_fontsize.y;
1538 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1540 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1542 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1543 _alpha *= panel_fg_alpha;
1545 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1549 panel_size.x += panel_bg_padding * 2; // restore initial width
1553 bool Scoreboard_WouldDraw()
1555 if (scoreboard_ui_enabled)
1557 if (intermission && scoreboard_ui_enabled == 2)
1559 HUD_Scoreboard_UI_Disable();
1564 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1566 else if (QuickMenu_IsOpened())
1568 else if (HUD_Radar_Clickable())
1570 else if (scoreboard_showscores)
1572 else if (intermission == 1)
1574 else if (intermission == 2)
1576 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1577 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1581 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1586 float average_accuracy;
1587 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1589 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1591 WepSet weapons_stat = WepSet_GetFromStat();
1592 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1593 int disownedcnt = 0;
1595 FOREACH(Weapons, it != WEP_Null, {
1596 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1598 WepSet set = it.m_wepset;
1599 if(it.spawnflags & WEP_TYPE_OTHER)
1604 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1606 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1613 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1614 if (weapon_cnt <= 0) return pos;
1617 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1619 int columns = ceil(weapon_cnt / rows);
1621 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1622 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1623 float height = weapon_height + hud_fontsize.y;
1625 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);
1626 pos.y += 1.25 * hud_fontsize.y;
1627 if(panel.current_panel_bg != "0")
1628 pos.y += panel_bg_border;
1631 panel_size.y = height * rows;
1632 panel_size.y += panel_bg_padding * 2;
1634 float panel_bg_alpha_save = panel_bg_alpha;
1635 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1637 panel_bg_alpha = panel_bg_alpha_save;
1639 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1640 if(panel.current_panel_bg != "0")
1641 end_pos.y += panel_bg_border * 2;
1643 if(panel_bg_padding)
1645 panel_pos += '1 1 0' * panel_bg_padding;
1646 panel_size -= '2 2 0' * panel_bg_padding;
1650 vector tmp = panel_size;
1652 float weapon_width = tmp.x / columns / rows;
1655 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1659 // column highlighting
1660 for (int i = 0; i < columns; ++i)
1662 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);
1665 for (int i = 0; i < rows; ++i)
1666 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1669 average_accuracy = 0;
1670 int weapons_with_stats = 0;
1672 pos.x += weapon_width / 2;
1674 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1677 Accuracy_LoadColors();
1679 float oldposx = pos.x;
1683 FOREACH(Weapons, it != WEP_Null, {
1684 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1686 WepSet set = it.m_wepset;
1687 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1689 if (it.spawnflags & WEP_TYPE_OTHER)
1693 if (weapon_stats >= 0)
1694 weapon_alpha = sbt_fg_alpha;
1696 weapon_alpha = 0.2 * sbt_fg_alpha;
1699 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1701 if (weapon_stats >= 0) {
1702 weapons_with_stats += 1;
1703 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1705 string s = sprintf("%d%%", weapon_stats * 100);
1706 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1708 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1709 rgb = Accuracy_GetColor(weapon_stats);
1711 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1713 tmpos.x += weapon_width * rows;
1714 pos.x += weapon_width * rows;
1715 if (rows == 2 && column == columns - 1) {
1723 if (weapons_with_stats)
1724 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1726 panel_size.x += panel_bg_padding * 2; // restore initial width
1731 bool is_item_filtered(entity it)
1733 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1735 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1738 if (it.instanceOfArmor || it.instanceOfHealth)
1740 int ha_mask = floor(mask) % 10;
1743 default: return false;
1744 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1745 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1746 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1747 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1750 if (it.instanceOfAmmo)
1752 int ammo_mask = floor(mask / 10) % 10;
1753 return (ammo_mask == 1);
1758 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1760 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1762 int disowned_cnt = 0;
1763 int uninteresting_cnt = 0;
1764 IL_EACH(default_order_items, true, {
1765 int q = g_inventory.inv_items[it.m_id];
1766 //q = 1; // debug: display all items
1767 if (is_item_filtered(it))
1768 ++uninteresting_cnt;
1772 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1773 int n = items_cnt - disowned_cnt;
1774 if (n <= 0) return pos;
1776 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1777 int columns = max(6, ceil(n / rows));
1779 float item_height = hud_fontsize.y * 2.3;
1780 float height = item_height + hud_fontsize.y;
1782 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1783 pos.y += 1.25 * hud_fontsize.y;
1784 if(panel.current_panel_bg != "0")
1785 pos.y += panel_bg_border;
1788 panel_size.y = height * rows;
1789 panel_size.y += panel_bg_padding * 2;
1791 float panel_bg_alpha_save = panel_bg_alpha;
1792 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1794 panel_bg_alpha = panel_bg_alpha_save;
1796 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1797 if(panel.current_panel_bg != "0")
1798 end_pos.y += panel_bg_border * 2;
1800 if(panel_bg_padding)
1802 panel_pos += '1 1 0' * panel_bg_padding;
1803 panel_size -= '2 2 0' * panel_bg_padding;
1807 vector tmp = panel_size;
1809 float item_width = tmp.x / columns / rows;
1812 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1816 // column highlighting
1817 for (int i = 0; i < columns; ++i)
1819 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);
1822 for (int i = 0; i < rows; ++i)
1823 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1827 pos.x += item_width / 2;
1829 float oldposx = pos.x;
1833 IL_EACH(default_order_items, !is_item_filtered(it), {
1834 int n = g_inventory.inv_items[it.m_id];
1835 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1836 if (n <= 0) continue;
1837 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);
1839 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1840 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1841 tmpos.x += item_width * rows;
1842 pos.x += item_width * rows;
1843 if (rows == 2 && column == columns - 1) {
1851 panel_size.x += panel_bg_padding * 2; // restore initial width
1856 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1858 pos.x += hud_fontsize.x * 0.25;
1859 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1860 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1861 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1863 pos.y += hud_fontsize.y;
1868 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1869 float stat_secrets_found, stat_secrets_total;
1870 float stat_monsters_killed, stat_monsters_total;
1874 // get monster stats
1875 stat_monsters_killed = STAT(MONSTERS_KILLED);
1876 stat_monsters_total = STAT(MONSTERS_TOTAL);
1878 // get secrets stats
1879 stat_secrets_found = STAT(SECRETS_FOUND);
1880 stat_secrets_total = STAT(SECRETS_TOTAL);
1882 // get number of rows
1883 if(stat_secrets_total)
1885 if(stat_monsters_total)
1888 // if no rows, return
1892 // draw table header
1893 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1894 pos.y += 1.25 * hud_fontsize.y;
1895 if(panel.current_panel_bg != "0")
1896 pos.y += panel_bg_border;
1899 panel_size.y = hud_fontsize.y * rows;
1900 panel_size.y += panel_bg_padding * 2;
1903 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1904 if(panel.current_panel_bg != "0")
1905 end_pos.y += panel_bg_border * 2;
1907 if(panel_bg_padding)
1909 panel_pos += '1 1 0' * panel_bg_padding;
1910 panel_size -= '2 2 0' * panel_bg_padding;
1914 vector tmp = panel_size;
1917 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1920 if(stat_monsters_total)
1922 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1923 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1927 if(stat_secrets_total)
1929 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1930 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1933 panel_size.x += panel_bg_padding * 2; // restore initial width
1937 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1940 RANKINGS_RECEIVED_CNT = 0;
1941 for (i=RANKINGS_CNT-1; i>=0; --i)
1943 ++RANKINGS_RECEIVED_CNT;
1945 if (RANKINGS_RECEIVED_CNT == 0)
1948 vector hl_rgb = rgb + '0.5 0.5 0.5';
1950 vector scoreboard_selected_hl_pos = pos;
1952 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1953 pos.y += 1.25 * hud_fontsize.y;
1954 if(panel.current_panel_bg != "0")
1955 pos.y += panel_bg_border;
1957 vector scoreboard_selected_hl_size = '0 0 0';
1958 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1959 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1964 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1966 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1971 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1973 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1977 float ranksize = 3 * hud_fontsize.x;
1978 float timesize = 5 * hud_fontsize.x;
1979 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1980 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1981 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1984 rankings_cnt = RANKINGS_RECEIVED_CNT;
1985 rankings_rows = ceil(rankings_cnt / rankings_columns);
1988 // expand name column to fill the entire row
1989 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1990 namesize += available_space;
1991 columnsize.x += available_space;
1993 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1994 panel_size.y += panel_bg_padding * 2;
1995 scoreboard_selected_hl_size.y += panel_size.y;
1999 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2000 if(panel.current_panel_bg != "0")
2001 end_pos.y += panel_bg_border * 2;
2003 if(panel_bg_padding)
2005 panel_pos += '1 1 0' * panel_bg_padding;
2006 panel_size -= '2 2 0' * panel_bg_padding;
2012 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2014 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2016 int column = 0, j = 0;
2017 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2018 int start_item = rankings_start_column * rankings_rows;
2019 for(i = start_item; i < start_item + rankings_cnt; ++i)
2021 int t = grecordtime[i];
2025 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2026 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2027 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2028 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2030 str = count_ordinal(i+1);
2031 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2032 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2033 str = ColorTranslateRGB(grecordholder[i]);
2035 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2036 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2038 pos.y += 1.25 * hud_fontsize.y;
2040 if(j >= rankings_rows)
2044 pos.x += panel_size.x / rankings_columns;
2045 pos.y = panel_pos.y;
2048 strfree(zoned_name_self);
2050 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2052 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2053 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2056 panel_size.x += panel_bg_padding * 2; // restore initial width
2060 bool have_weapon_stats;
2061 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2063 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2065 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2068 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2069 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2075 if (!have_weapon_stats)
2077 FOREACH(Weapons, it != WEP_Null, {
2078 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2079 if (weapon_stats >= 0)
2081 have_weapon_stats = true;
2085 if (!have_weapon_stats)
2092 bool have_item_stats;
2093 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2095 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2097 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2100 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2101 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2107 if (!have_item_stats)
2109 IL_EACH(default_order_items, true, {
2110 if (!is_item_filtered(it))
2112 int q = g_inventory.inv_items[it.m_id];
2113 //q = 1; // debug: display all items
2116 have_item_stats = true;
2121 if (!have_item_stats)
2128 vector Scoreboard_Spectators_Draw(vector pos) {
2133 for(pl = players.sort_next; pl; pl = pl.sort_next)
2135 if(pl.team == NUM_SPECTATOR)
2137 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2138 if(tm.team == NUM_SPECTATOR)
2140 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2141 draw_beginBoldFont();
2142 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2144 pos.y += 1.25 * hud_fontsize.y;
2146 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2147 pos.y += 1.25 * hud_fontsize.y;
2152 if (str != "") // if there's at least one spectator
2153 pos.y += 0.5 * hud_fontsize.y;
2158 void Scoreboard_Draw()
2160 if(!autocvar__hud_configure)
2162 if(!hud_draw_maximized) return;
2164 // frametime checks allow to toggle the scoreboard even when the game is paused
2165 if(scoreboard_active) {
2166 if (scoreboard_fade_alpha == 0)
2167 scoreboard_time = time;
2168 if(hud_configure_menu_open == 1)
2169 scoreboard_fade_alpha = 1;
2170 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2171 if (scoreboard_fadeinspeed && frametime)
2172 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2174 scoreboard_fade_alpha = 1;
2175 if(hud_fontsize_str != autocvar_hud_fontsize)
2177 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2178 Scoreboard_initFieldSizes();
2179 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2183 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2184 if (scoreboard_fadeoutspeed && frametime)
2185 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2187 scoreboard_fade_alpha = 0;
2190 if (!scoreboard_fade_alpha)
2192 scoreboard_acc_fade_alpha = 0;
2193 scoreboard_itemstats_fade_alpha = 0;
2198 scoreboard_fade_alpha = 0;
2200 if (autocvar_hud_panel_scoreboard_dynamichud)
2203 HUD_Scale_Disable();
2205 if(scoreboard_fade_alpha <= 0)
2207 panel_fade_alpha *= scoreboard_fade_alpha;
2208 HUD_Panel_LoadCvars();
2210 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2211 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2212 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2213 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2214 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2215 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2216 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2218 // don't overlap with con_notify
2219 if(!autocvar__hud_configure)
2220 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2222 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2223 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2224 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2225 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2226 panel_pos.x = scoreboard_left;
2227 panel_size.x = fixed_scoreboard_width;
2229 Scoreboard_UpdatePlayerTeams();
2231 scoreboard_top = panel_pos.y;
2232 vector pos = panel_pos;
2237 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2239 // Begin of Game Info Section
2240 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2241 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2243 // Game Info: Game Type
2244 if (scoreboard_ui_enabled == 2)
2245 str = _("Team Selection");
2247 str = MapInfo_Type_ToText(gametype);
2248 draw_beginBoldFont();
2249 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);
2252 pos.y += sb_gameinfo_type_fontsize.y;
2253 // Game Info: Game Detail
2254 if (scoreboard_ui_enabled == 2)
2256 if (scoreboard_selected_team)
2257 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2259 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2260 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);
2262 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2263 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2264 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);
2268 float tl = STAT(TIMELIMIT);
2269 float fl = STAT(FRAGLIMIT);
2270 float ll = STAT(LEADLIMIT);
2271 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2274 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2275 if(!gametype.m_hidelimits)
2280 str = strcat(str, "^7 / "); // delimiter
2283 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2284 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2285 (teamscores_label(ts_primary) == "fastest") ? "" :
2286 TranslateScoresLabel(teamscores_label(ts_primary))));
2290 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2291 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2292 (scores_label(ps_primary) == "fastest") ? "" :
2293 TranslateScoresLabel(scores_label(ps_primary))));
2298 if(tl > 0 || fl > 0)
2301 if (ll_and_fl && fl > 0)
2302 str = strcat(str, "^7 & ");
2304 str = strcat(str, "^7 / ");
2309 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2310 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2311 (teamscores_label(ts_primary) == "fastest") ? "" :
2312 TranslateScoresLabel(teamscores_label(ts_primary))));
2316 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2317 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2318 (scores_label(ps_primary) == "fastest") ? "" :
2319 TranslateScoresLabel(scores_label(ps_primary))));
2323 drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
2325 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2326 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2328 // End of Game Info Section
2330 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2331 if(panel.current_panel_bg != "0")
2332 pos.y += panel_bg_border;
2334 // Draw the scoreboard
2335 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2338 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2342 vector panel_bg_color_save = panel_bg_color;
2343 vector team_score_baseoffset;
2344 vector team_size_baseoffset;
2345 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2347 // put team score to the left of scoreboard (and team size to the right)
2348 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2349 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2350 if(panel.current_panel_bg != "0")
2352 team_score_baseoffset.x -= panel_bg_border;
2353 team_size_baseoffset.x += panel_bg_border;
2358 // put team score to the right of scoreboard (and team size to the left)
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;
2368 int team_size_total = 0;
2369 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2371 // calculate team size total (sum of all team sizes)
2372 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2373 if(tm.team != NUM_SPECTATOR)
2374 team_size_total += tm.team_size;
2377 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2379 if(tm.team == NUM_SPECTATOR)
2384 draw_beginBoldFont();
2385 vector rgb = Team_ColorRGB(tm.team);
2386 str = ftos(tm.(teamscores(ts_primary)));
2387 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2389 // team score on the left (default)
2390 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2394 // team score on the right
2395 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2397 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2399 // team size (if set to show on the side)
2400 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2402 // calculate the starting position for the whole team size info string
2403 str = sprintf("%d/%d", tm.team_size, team_size_total);
2404 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2406 // team size on the left
2407 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2411 // team size on the right
2412 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2414 str = sprintf("%d", tm.team_size);
2415 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2416 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2417 str = sprintf("/%d", team_size_total);
2418 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2422 // secondary score, e.g. keyhunt
2423 if(ts_primary != ts_secondary)
2425 str = ftos(tm.(teamscores(ts_secondary)));
2426 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2429 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2434 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2437 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2440 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2441 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2442 else if(panel_bg_color_team > 0)
2443 panel_bg_color = rgb * panel_bg_color_team;
2445 panel_bg_color = rgb;
2446 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2448 panel_bg_color = panel_bg_color_save;
2452 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2453 if(tm.team != NUM_SPECTATOR)
2456 // display it anyway
2457 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2460 // draw scoreboard spectators before accuracy and item stats
2461 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2462 pos = Scoreboard_Spectators_Draw(pos);
2465 // draw accuracy and item stats
2466 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2467 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2468 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2469 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2471 // draw scoreboard spectators after accuracy and item stats and before rankings
2472 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2473 pos = Scoreboard_Spectators_Draw(pos);
2476 if(MUTATOR_CALLHOOK(ShowRankings)) {
2477 string ranktitle = M_ARGV(0, string);
2478 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2479 if(race_speedaward_alltimebest)
2482 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2486 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2487 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2488 str = strcat(str, " / ");
2490 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2491 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2492 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2493 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2495 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2500 // draw scoreboard spectators after rankings
2501 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2502 pos = Scoreboard_Spectators_Draw(pos);
2505 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2507 // draw scoreboard spectators after mapstats
2508 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2509 pos = Scoreboard_Spectators_Draw(pos);
2513 // print information about respawn status
2514 float respawn_time = STAT(RESPAWN_TIME);
2518 if(respawn_time < 0)
2520 // a negative number means we are awaiting respawn, time value is still the same
2521 respawn_time *= -1; // remove mark now that we checked it
2523 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2524 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2526 str = sprintf(_("^1Respawning in ^3%s^1..."),
2527 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2528 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2530 count_seconds(ceil(respawn_time - time))
2534 else if(time < respawn_time)
2536 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
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)
2545 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2547 pos.y += 1.2 * hud_fontsize.y;
2548 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2551 pos.y += hud_fontsize.y;
2552 if (scoreboard_fade_alpha < 1)
2553 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2554 else if (pos.y != scoreboard_bottom)
2556 if (pos.y > scoreboard_bottom)
2557 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2559 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2564 if (scoreboard_fade_alpha == 1)
2566 if (scoreboard_bottom > 0.95 * vid_conheight)
2567 rankings_rows = max(1, rankings_rows - 1);
2568 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2569 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2571 rankings_cnt = rankings_rows * rankings_columns;