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 bool scoreboard_ui_disabling;
170 void HUD_Scoreboard_UI_Disable()
172 scoreboard_ui_disabling = true;
173 scoreboard_showscores = false;
176 void HUD_Scoreboard_UI_Disable_Instantly()
178 scoreboard_ui_disabling = false;
179 scoreboard_ui_enabled = 0;
180 scoreboard_selected_panel = 0;
181 scoreboard_selected_player = NULL;
182 scoreboard_selected_team = NULL;
185 // mode: 0 normal, 1 team selection
186 void Scoreboard_UI_Enable(int mode)
190 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
193 // release player's pressed keys as they aren't released elsewhere
194 // in particular jump needs to be released as it may open the team selection
195 // (when server detects jump has been pressed it sends the command to open the team selection)
196 Release_Common_Keys();
197 scoreboard_ui_enabled = 2;
198 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
202 if (scoreboard_ui_enabled == 1)
204 scoreboard_ui_enabled = 1;
205 scoreboard_selected_panel = SB_PANEL_FIRST;
207 scoreboard_selected_player = NULL;
208 scoreboard_selected_team = NULL;
209 scoreboard_selected_panel_time = time;
212 int rankings_start_column;
213 int rankings_rows = 0;
214 int rankings_columns = 0;
215 int rankings_cnt = 0;
216 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
220 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
225 mousepos.x = nPrimary;
226 mousepos.y = nSecondary;
233 // at this point bInputType can only be 0 or 1 (key pressed or released)
234 bool key_pressed = (bInputType == 0);
236 // ESC to exit (TAB-ESC works too)
237 if(nPrimary == K_ESCAPE)
241 HUD_Scoreboard_UI_Disable();
245 // block any input while a menu dialog is fading
246 if(autocvar__menu_alpha)
252 // allow console bind to work
253 string con_keys = findkeysforcommand("toggleconsole", 0);
254 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
256 bool hit_con_bind = false;
258 for (i = 0; i < keys; ++i)
260 if(nPrimary == stof(argv(i)))
265 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
266 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
267 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
268 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
271 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
272 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
273 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
274 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
277 if(nPrimary == K_TAB)
281 if (scoreboard_ui_enabled == 2)
283 if (hudShiftState & S_SHIFT)
286 goto downarrow_action;
289 if (hudShiftState & S_SHIFT)
291 --scoreboard_selected_panel;
292 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
293 --scoreboard_selected_panel;
294 if (scoreboard_selected_panel < SB_PANEL_FIRST)
295 scoreboard_selected_panel = SB_PANEL_MAX;
299 ++scoreboard_selected_panel;
300 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
301 ++scoreboard_selected_panel;
302 if (scoreboard_selected_panel > SB_PANEL_MAX)
303 scoreboard_selected_panel = SB_PANEL_FIRST;
306 scoreboard_selected_panel_time = time;
308 else if(nPrimary == K_DOWNARROW)
312 LABEL(downarrow_action);
313 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
315 if (scoreboard_ui_enabled == 2)
317 entity curr_team = NULL;
318 bool scoreboard_selected_team_found = false;
319 if (!scoreboard_selected_team)
320 scoreboard_selected_team_found = true;
322 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
324 if(tm.team == NUM_SPECTATOR)
327 if (scoreboard_selected_team_found)
329 if (scoreboard_selected_team == tm)
330 scoreboard_selected_team_found = true;
333 if (curr_team == scoreboard_selected_team) // loop reached the last team
335 scoreboard_selected_team = curr_team;
340 entity curr_pl = NULL;
341 bool scoreboard_selected_player_found = false;
342 if (!scoreboard_selected_player)
343 scoreboard_selected_player_found = true;
345 for(tm = teams.sort_next; tm; tm = tm.sort_next)
347 if(tm.team != NUM_SPECTATOR)
348 for(pl = players.sort_next; pl; pl = pl.sort_next)
350 if(pl.team != tm.team)
353 if (scoreboard_selected_player_found)
355 if (scoreboard_selected_player == pl)
356 scoreboard_selected_player_found = true;
360 if (curr_pl == scoreboard_selected_player) // loop reached the last player
362 scoreboard_selected_player = curr_pl;
366 else if(nPrimary == K_UPARROW)
370 LABEL(uparrow_action);
371 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
373 if (scoreboard_ui_enabled == 2)
375 entity prev_team = NULL;
376 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
378 if(tm.team == NUM_SPECTATOR)
380 if (tm == scoreboard_selected_team)
385 scoreboard_selected_team = prev_team;
389 entity prev_pl = NULL;
391 for(tm = teams.sort_next; tm; tm = tm.sort_next)
393 if(tm.team != NUM_SPECTATOR)
394 for(pl = players.sort_next; pl; pl = pl.sort_next)
396 if(pl.team != tm.team)
398 if (pl == scoreboard_selected_player)
404 scoreboard_selected_player = prev_pl;
408 else if(nPrimary == K_RIGHTARROW)
412 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
413 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
415 else if(nPrimary == K_LEFTARROW)
419 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
420 rankings_start_column = max(rankings_start_column - 1, 0);
422 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
426 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
428 if (scoreboard_ui_enabled == 2)
431 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
434 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
435 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
436 HUD_Scoreboard_UI_Disable();
438 else if (scoreboard_selected_player)
439 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
442 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
446 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
448 switch (scoreboard_selected_columns_layout)
451 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
453 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
454 scoreboard_selected_columns_layout = 1;
459 localcmd(sprintf("scoreboard_columns_set default\n"));
460 scoreboard_selected_columns_layout = 2;
463 localcmd(sprintf("scoreboard_columns_set all\n"));
464 scoreboard_selected_columns_layout = 0;
469 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
473 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
475 if (scoreboard_selected_player)
477 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
478 HUD_Scoreboard_UI_Disable();
482 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
486 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
488 if (scoreboard_selected_player)
489 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
492 else if(hit_con_bind || nPrimary == K_PAUSE)
498 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
499 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
501 #define SB_EXTRA_SORTING_FIELDS 5
502 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
503 void Scoreboard_InitScores()
507 ps_primary = ps_secondary = NULL;
508 ts_primary = ts_secondary = -1;
509 FOREACH(Scores, true, {
510 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
511 if(f == SFL_SORT_PRIO_PRIMARY)
513 if(f == SFL_SORT_PRIO_SECONDARY)
515 if(ps_primary == it || ps_secondary == it)
517 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
518 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
519 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
520 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
521 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
523 if(ps_secondary == NULL)
524 ps_secondary = ps_primary;
526 for(i = 0; i < MAX_TEAMSCORE; ++i)
528 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
529 if(f == SFL_SORT_PRIO_PRIMARY)
531 if(f == SFL_SORT_PRIO_SECONDARY)
534 if(ts_secondary == -1)
535 ts_secondary = ts_primary;
537 Cmd_Scoreboard_SetFields(0);
541 void Scoreboard_UpdatePlayerTeams()
543 static float update_time;
544 if (time <= update_time)
550 for(pl = players.sort_next; pl; pl = pl.sort_next)
553 int Team = entcs_GetScoreTeam(pl.sv_entnum);
554 if(SetTeam(pl, Team))
557 Scoreboard_UpdatePlayerPos(pl);
561 pl = players.sort_next;
566 print(strcat("PNUM: ", ftos(num), "\n"));
571 int Scoreboard_CompareScore(int vl, int vr, int f)
573 TC(int, vl); TC(int, vr); TC(int, f);
574 if(f & SFL_ZERO_IS_WORST)
576 if(vl == 0 && vr != 0)
578 if(vl != 0 && vr == 0)
582 return IS_INCREASING(f);
584 return IS_DECREASING(f);
588 float Scoreboard_ComparePlayerScores(entity left, entity right)
590 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
591 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
598 if(vl == NUM_SPECTATOR)
600 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
602 if(!left.gotscores && right.gotscores)
609 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
613 if (!fld) fld = ps_primary;
614 else if (ps_secondary == ps_primary) continue;
615 else fld = ps_secondary;
619 fld = sb_extra_sorting_field[i];
620 if (fld == ps_primary || fld == ps_secondary) continue;
624 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
625 if (r >= 0) return r;
628 if (left.sv_entnum < right.sv_entnum)
634 void Scoreboard_UpdatePlayerPos(entity player)
637 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
639 SORT_SWAP(player, ent);
641 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
643 SORT_SWAP(ent, player);
647 float Scoreboard_CompareTeamScores(entity left, entity right)
649 if(left.team == NUM_SPECTATOR)
651 if(right.team == NUM_SPECTATOR)
656 for(int i = -2; i < MAX_TEAMSCORE; ++i)
660 if (fld_idx == -1) fld_idx = ts_primary;
661 else if (ts_secondary == ts_primary) continue;
662 else fld_idx = ts_secondary;
667 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
670 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
671 if (r >= 0) return r;
674 if (left.team < right.team)
680 void Scoreboard_UpdateTeamPos(entity Team)
683 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
685 SORT_SWAP(Team, ent);
687 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
689 SORT_SWAP(ent, Team);
693 void Cmd_Scoreboard_Help()
695 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
696 LOG_HELP(_("Usage:"));
697 LOG_HELP("^2scoreboard_columns_set ^3default");
698 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
699 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
700 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
701 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
702 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
703 LOG_HELP(_("The following field names are recognized (case insensitive):"));
709 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
710 "of game types, then a slash, to make the field show up only in these\n"
711 "or in all but these game types. You can also specify 'all' as a\n"
712 "field to show all fields available for the current game mode."));
715 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
716 "include/exclude ALL teams/noteams game modes."));
719 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
720 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
721 "right of the vertical bar aligned to the right."));
722 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
723 "other gamemodes except DM."));
726 // NOTE: adding a gametype with ? to not warn for an optional field
727 // make sure it's excluded in a previous exclusive rule, if any
728 // otherwise the previous exclusive rule warns anyway
729 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
730 #define SCOREBOARD_DEFAULT_COLUMNS \
731 "ping pl fps name |" \
732 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
733 " -teams,lms/deaths +ft,tdm/deaths" \
735 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
736 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
737 " +tdm,ft,dom,ons,as/teamkills"\
738 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
739 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
740 " +lms/lives +lms/rank" \
741 " +kh/kckills +kh/losses +kh/caps" \
742 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
743 " +as/objectives +nb/faults +nb/goals" \
744 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
745 " +dom/ticks +dom/takes" \
746 " -lms,rc,cts,inv,nb/score"
748 void Cmd_Scoreboard_SetFields(int argc)
753 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
757 return; // do nothing, we don't know gametype and scores yet
759 // sbt_fields uses strunzone on the titles!
760 if(!sbt_field_title[0])
761 for(i = 0; i < MAX_SBT_FIELDS; ++i)
762 sbt_field_title[i] = strzone("(null)");
764 // TODO: re enable with gametype dependant cvars?
765 if(argc < 3) // no arguments provided
766 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
769 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
773 if(argv(2) == "default" || argv(2) == "expand_default")
775 if(argv(2) == "expand_default")
776 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
777 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
779 else if(argv(2) == "all" || argv(2) == "ALL")
781 string s = "ping pl name |"; // scores without label (not really scores)
784 // scores without label
785 s = strcat(s, " ", "sum");
786 s = strcat(s, " ", "kdratio");
787 s = strcat(s, " ", "frags");
789 FOREACH(Scores, true, {
791 if(it != ps_secondary)
792 if(scores_label(it) != "")
793 s = strcat(s, " ", scores_label(it));
795 if(ps_secondary != ps_primary)
796 s = strcat(s, " ", scores_label(ps_secondary));
797 s = strcat(s, " ", scores_label(ps_primary));
798 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
805 hud_fontsize = HUD_GetFontsize("hud_fontsize");
807 for(i = 1; i < argc - 1; ++i)
810 bool nocomplain = false;
811 if(substring(str, 0, 1) == "?")
814 str = substring(str, 1, strlen(str) - 1);
817 slash = strstrofs(str, "/", 0);
820 pattern = substring(str, 0, slash);
821 str = substring(str, slash + 1, strlen(str) - (slash + 1));
823 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
827 str = strtolower(str);
828 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
829 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
834 // fields without a label (not networked via the score system)
835 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
836 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
837 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
838 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
839 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
840 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
841 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
842 default: // fields with a label
844 // map alternative labels
845 if (str == "damage") str = "dmg";
846 if (str == "damagetaken") str = "dmgtaken";
848 FOREACH(Scores, true, {
849 if (str == strtolower(scores_label(it))) {
851 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
855 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
856 if(!nocomplain && str != "fps") // server can disable the fps field
857 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
859 strfree(sbt_field_title[sbt_num_fields]);
860 sbt_field_size[sbt_num_fields] = 0;
864 sbt_field[sbt_num_fields] = j;
867 if(j == ps_secondary)
868 have_secondary = true;
873 if(sbt_num_fields >= MAX_SBT_FIELDS)
877 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
879 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
880 have_secondary = true;
881 if(ps_primary == ps_secondary)
882 have_secondary = true;
883 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
885 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
889 strfree(sbt_field_title[sbt_num_fields]);
890 for(i = sbt_num_fields; i > 0; --i)
892 sbt_field_title[i] = sbt_field_title[i-1];
893 sbt_field_size[i] = sbt_field_size[i-1];
894 sbt_field[i] = sbt_field[i-1];
896 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
897 sbt_field[0] = SP_NAME;
899 LOG_INFO("fixed missing field 'name'");
903 strfree(sbt_field_title[sbt_num_fields]);
904 for(i = sbt_num_fields; i > 1; --i)
906 sbt_field_title[i] = sbt_field_title[i-1];
907 sbt_field_size[i] = sbt_field_size[i-1];
908 sbt_field[i] = sbt_field[i-1];
910 sbt_field_title[1] = strzone("|");
911 sbt_field[1] = SP_SEPARATOR;
912 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
914 LOG_INFO("fixed missing field '|'");
917 else if(!have_separator)
919 strcpy(sbt_field_title[sbt_num_fields], "|");
920 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
921 sbt_field[sbt_num_fields] = SP_SEPARATOR;
923 LOG_INFO("fixed missing field '|'");
927 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
928 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
929 sbt_field[sbt_num_fields] = ps_secondary;
931 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
935 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
936 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
937 sbt_field[sbt_num_fields] = ps_primary;
939 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
943 sbt_field[sbt_num_fields] = SP_END;
946 string Scoreboard_AddPlayerId(string pl_name, entity pl)
948 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
949 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
950 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
954 vector sbt_field_rgb;
955 string sbt_field_icon0;
956 string sbt_field_icon1;
957 string sbt_field_icon2;
958 vector sbt_field_icon0_rgb;
959 vector sbt_field_icon1_rgb;
960 vector sbt_field_icon2_rgb;
961 string Scoreboard_GetName(entity pl)
963 if(ready_waiting && pl.ready)
965 sbt_field_icon0 = "gfx/scoreboard/player_ready";
969 int f = entcs_GetClientColors(pl.sv_entnum);
971 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
972 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
973 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
974 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
975 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
978 return entcs_GetName(pl.sv_entnum);
981 string Scoreboard_GetField(entity pl, PlayerScoreField field)
983 float tmp, num, denom;
986 sbt_field_rgb = '1 1 1';
987 sbt_field_icon0 = "";
988 sbt_field_icon1 = "";
989 sbt_field_icon2 = "";
990 sbt_field_icon0_rgb = '1 1 1';
991 sbt_field_icon1_rgb = '1 1 1';
992 sbt_field_icon2_rgb = '1 1 1';
997 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
998 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1002 tmp = max(0, min(220, f-80)) / 220;
1003 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1009 f = pl.ping_packetloss;
1010 tmp = pl.ping_movementloss;
1011 if(f == 0 && tmp == 0)
1013 str = ftos(ceil(f * 100));
1015 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1016 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1017 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1021 str = Scoreboard_GetName(pl);
1022 if (autocvar_hud_panel_scoreboard_playerid)
1023 str = Scoreboard_AddPlayerId(str, pl);
1027 f = pl.(scores(SP_KILLS));
1028 f -= pl.(scores(SP_SUICIDES));
1032 num = pl.(scores(SP_KILLS));
1033 denom = pl.(scores(SP_DEATHS));
1036 sbt_field_rgb = '0 1 0';
1037 str = sprintf("%d", num);
1038 } else if(num <= 0) {
1039 sbt_field_rgb = '1 0 0';
1040 str = sprintf("%.1f", num/denom);
1042 str = sprintf("%.1f", num/denom);
1046 f = pl.(scores(SP_KILLS));
1047 f -= pl.(scores(SP_DEATHS));
1050 sbt_field_rgb = '0 1 0';
1052 sbt_field_rgb = '1 1 1';
1054 sbt_field_rgb = '1 0 0';
1060 float elo = pl.(scores(SP_ELO));
1062 case -1: return "...";
1063 case -2: return _("N/A");
1064 default: return ftos(elo);
1070 float fps = pl.(scores(SP_FPS));
1073 sbt_field_rgb = '1 1 1';
1074 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1076 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1077 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1081 case SP_DMG: case SP_DMGTAKEN:
1082 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1084 default: case SP_SCORE:
1085 tmp = pl.(scores(field));
1086 f = scores_flags(field);
1087 if(field == ps_primary)
1088 sbt_field_rgb = '1 1 0';
1089 else if(field == ps_secondary)
1090 sbt_field_rgb = '0 1 1';
1092 sbt_field_rgb = '1 1 1';
1093 return ScoreString(f, tmp);
1098 float sbt_fixcolumnwidth_len;
1099 float sbt_fixcolumnwidth_iconlen;
1100 float sbt_fixcolumnwidth_marginlen;
1102 string Scoreboard_FixColumnWidth(int i, string str)
1108 sbt_fixcolumnwidth_iconlen = 0;
1110 if(sbt_field_icon0 != "")
1112 sz = draw_getimagesize(sbt_field_icon0);
1114 if(sbt_fixcolumnwidth_iconlen < f)
1115 sbt_fixcolumnwidth_iconlen = f;
1118 if(sbt_field_icon1 != "")
1120 sz = draw_getimagesize(sbt_field_icon1);
1122 if(sbt_fixcolumnwidth_iconlen < f)
1123 sbt_fixcolumnwidth_iconlen = f;
1126 if(sbt_field_icon2 != "")
1128 sz = draw_getimagesize(sbt_field_icon2);
1130 if(sbt_fixcolumnwidth_iconlen < f)
1131 sbt_fixcolumnwidth_iconlen = f;
1134 if(sbt_fixcolumnwidth_iconlen != 0)
1136 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1137 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1140 sbt_fixcolumnwidth_marginlen = 0;
1142 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1145 float remaining_space = 0;
1146 for(j = 0; j < sbt_num_fields; ++j)
1148 if (sbt_field[i] != SP_SEPARATOR)
1149 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1150 sbt_field_size[i] = panel_size.x - remaining_space;
1152 if (sbt_fixcolumnwidth_iconlen != 0)
1153 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1154 float namesize = panel_size.x - remaining_space;
1155 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1156 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1158 max_namesize = vid_conwidth - remaining_space;
1161 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1163 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1164 if(sbt_field_size[i] < f)
1165 sbt_field_size[i] = f;
1170 void Scoreboard_initFieldSizes()
1172 for(int i = 0; i < sbt_num_fields; ++i)
1174 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1175 Scoreboard_FixColumnWidth(i, "");
1179 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1182 vector column_dim = eY * panel_size.y;
1184 column_dim.y -= 1.25 * hud_fontsize.y;
1185 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1186 pos.x += hud_fontsize.x * 0.5;
1187 for(i = 0; i < sbt_num_fields; ++i)
1189 if(sbt_field[i] == SP_SEPARATOR)
1191 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1194 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1195 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1196 pos.x += column_dim.x;
1198 if(sbt_field[i] == SP_SEPARATOR)
1200 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1201 for(i = sbt_num_fields - 1; i > 0; --i)
1203 if(sbt_field[i] == SP_SEPARATOR)
1206 pos.x -= sbt_field_size[i];
1211 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1212 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1215 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1216 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1217 pos.x -= hud_fontsize.x;
1221 pos.x = panel_pos.x;
1222 pos.y += 1.25 * hud_fontsize.y;
1226 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1228 TC(bool, is_self); TC(int, pl_number);
1230 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1232 vector h_pos = item_pos;
1233 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1234 // alternated rows highlighting
1235 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1237 if (pl == scoreboard_selected_player)
1238 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1241 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1242 else if((sbt_highlight) && (!(pl_number % 2)))
1243 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1245 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1247 vector pos = item_pos;
1248 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1250 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1252 pos.x += hud_fontsize.x * 0.5;
1253 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1254 vector tmp = '0 0 0';
1256 PlayerScoreField field;
1257 for(i = 0; i < sbt_num_fields; ++i)
1259 field = sbt_field[i];
1260 if(field == SP_SEPARATOR)
1263 if(is_spec && field != SP_NAME && field != SP_PING) {
1264 pos.x += sbt_field_size[i] + hud_fontsize.x;
1267 str = Scoreboard_GetField(pl, field);
1268 str = Scoreboard_FixColumnWidth(i, str);
1270 pos.x += sbt_field_size[i] + hud_fontsize.x;
1272 if(field == SP_NAME) {
1273 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1274 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1276 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1277 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1280 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1281 if(sbt_field_icon0 != "")
1282 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1283 if(sbt_field_icon1 != "")
1284 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1285 if(sbt_field_icon2 != "")
1286 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1289 if(sbt_field[i] == SP_SEPARATOR)
1291 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1292 for(i = sbt_num_fields-1; i > 0; --i)
1294 field = sbt_field[i];
1295 if(field == SP_SEPARATOR)
1298 if(is_spec && field != SP_NAME && field != SP_PING) {
1299 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1303 str = Scoreboard_GetField(pl, field);
1304 str = Scoreboard_FixColumnWidth(i, str);
1306 if(field == SP_NAME) {
1307 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1308 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1310 tmp.x = sbt_fixcolumnwidth_len;
1311 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1314 tmp.x = sbt_field_size[i];
1315 if(sbt_field_icon0 != "")
1316 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1317 if(sbt_field_icon1 != "")
1318 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1319 if(sbt_field_icon2 != "")
1320 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1321 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1326 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1329 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1332 vector h_pos = item_pos;
1333 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1335 bool complete = (this_team == NUM_SPECTATOR);
1338 if((sbt_highlight) && (!(pl_number % 2)))
1339 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1341 vector pos = item_pos;
1342 pos.x += hud_fontsize.x * 0.5;
1343 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1345 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1347 width_limit -= stringwidth("...", false, hud_fontsize);
1348 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1349 static float max_name_width = 0;
1351 float fieldsize = 0;
1352 float min_fieldsize = 0;
1353 float fieldpadding = hud_fontsize.x * 0.25;
1354 if(this_team == NUM_SPECTATOR)
1356 if(autocvar_hud_panel_scoreboard_spectators_showping)
1357 min_fieldsize = stringwidth("999", false, hud_fontsize);
1359 else if(autocvar_hud_panel_scoreboard_others_showscore)
1360 min_fieldsize = stringwidth("99", false, hud_fontsize);
1361 for(i = 0; pl; pl = pl.sort_next)
1363 if(pl.team != this_team)
1365 if(pl == ignored_pl)
1369 if(this_team == NUM_SPECTATOR)
1371 if(autocvar_hud_panel_scoreboard_spectators_showping)
1372 field = Scoreboard_GetField(pl, SP_PING);
1374 else if(autocvar_hud_panel_scoreboard_others_showscore)
1375 field = Scoreboard_GetField(pl, SP_SCORE);
1377 string str = entcs_GetName(pl.sv_entnum);
1378 if (autocvar_hud_panel_scoreboard_playerid)
1379 str = Scoreboard_AddPlayerId(str, pl);
1380 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1381 float column_width = stringwidth(str, true, hud_fontsize);
1382 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1384 if(column_width > max_name_width)
1385 max_name_width = column_width;
1386 column_width = max_name_width;
1390 fieldsize = stringwidth(field, false, hud_fontsize);
1391 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1394 if(pos.x + column_width > width_limit)
1399 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1404 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1405 pos.y += hud_fontsize.y * 1.25;
1409 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1411 if (pl == scoreboard_selected_player)
1413 h_size.x = column_width + hud_fontsize.x * 0.25;
1414 h_size.y = hud_fontsize.y;
1415 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1419 vector name_pos = pos;
1420 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1421 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1422 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1425 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1426 h_size.y = hud_fontsize.y;
1427 vector field_pos = pos;
1428 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1429 field_pos.x += column_width - h_size.x;
1431 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1432 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1433 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1437 h_size.x = column_width + hud_fontsize.x * 0.25;
1438 h_size.y = hud_fontsize.y;
1439 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1441 pos.x += column_width;
1442 pos.x += hud_fontsize.x;
1444 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1447 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1449 int max_players = 999;
1450 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1452 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1455 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1456 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1457 height /= team_count;
1460 height -= panel_bg_padding * 2; // - padding
1461 max_players = floor(height / (hud_fontsize.y * 1.25));
1462 if(max_players <= 1)
1464 if(max_players == tm.team_size)
1469 entity me = playerslots[current_player];
1471 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1472 panel_size.y += panel_bg_padding * 2;
1474 vector scoreboard_selected_hl_pos = pos;
1475 vector scoreboard_selected_hl_size = '0 0 0';
1476 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1477 scoreboard_selected_hl_size.y = panel_size.y;
1481 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1482 if(panel.current_panel_bg != "0")
1483 end_pos.y += panel_bg_border * 2;
1485 if(panel_bg_padding)
1487 panel_pos += '1 1 0' * panel_bg_padding;
1488 panel_size -= '2 2 0' * panel_bg_padding;
1492 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1496 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1498 pos.y += 1.25 * hud_fontsize.y;
1501 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1503 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1506 // print header row and highlight columns
1507 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1509 // fill the table and draw the rows
1510 bool is_self = false;
1511 bool self_shown = false;
1513 for(pl = players.sort_next; pl; pl = pl.sort_next)
1515 if(pl.team != tm.team)
1517 if(i == max_players - 2 && pl != me)
1519 if(!self_shown && me.team == tm.team)
1521 Scoreboard_DrawItem(pos, rgb, me, true, i);
1523 pos.y += 1.25 * hud_fontsize.y;
1527 if(i >= max_players - 1)
1529 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1532 is_self = (pl.sv_entnum == current_player);
1533 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1536 pos.y += 1.25 * hud_fontsize.y;
1540 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1542 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1544 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1545 _alpha *= panel_fg_alpha;
1547 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1551 panel_size.x += panel_bg_padding * 2; // restore initial width
1555 bool Scoreboard_WouldDraw()
1557 if (scoreboard_ui_enabled)
1559 if (scoreboard_ui_disabling)
1561 if (scoreboard_fade_alpha == 0)
1562 HUD_Scoreboard_UI_Disable_Instantly();
1565 if (intermission && scoreboard_ui_enabled == 2)
1567 HUD_Scoreboard_UI_Disable_Instantly();
1572 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1574 else if (QuickMenu_IsOpened())
1576 else if (HUD_Radar_Clickable())
1578 else if (scoreboard_showscores)
1580 else if (intermission == 1)
1582 else if (intermission == 2)
1584 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1585 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1589 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1594 float average_accuracy;
1595 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1597 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1599 WepSet weapons_stat = WepSet_GetFromStat();
1600 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1601 int disownedcnt = 0;
1603 FOREACH(Weapons, it != WEP_Null, {
1604 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1606 WepSet set = it.m_wepset;
1607 if(it.spawnflags & WEP_TYPE_OTHER)
1612 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1614 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1621 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1622 if (weapon_cnt <= 0) return pos;
1625 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1627 int columns = ceil(weapon_cnt / rows);
1629 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1630 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1631 float height = weapon_height + hud_fontsize.y;
1633 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);
1634 pos.y += 1.25 * hud_fontsize.y;
1635 if(panel.current_panel_bg != "0")
1636 pos.y += panel_bg_border;
1639 panel_size.y = height * rows;
1640 panel_size.y += panel_bg_padding * 2;
1642 float panel_bg_alpha_save = panel_bg_alpha;
1643 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1645 panel_bg_alpha = panel_bg_alpha_save;
1647 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1648 if(panel.current_panel_bg != "0")
1649 end_pos.y += panel_bg_border * 2;
1651 if(panel_bg_padding)
1653 panel_pos += '1 1 0' * panel_bg_padding;
1654 panel_size -= '2 2 0' * panel_bg_padding;
1658 vector tmp = panel_size;
1660 float weapon_width = tmp.x / columns / rows;
1663 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1667 // column highlighting
1668 for (int i = 0; i < columns; ++i)
1670 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);
1673 for (int i = 0; i < rows; ++i)
1674 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1677 average_accuracy = 0;
1678 int weapons_with_stats = 0;
1680 pos.x += weapon_width / 2;
1682 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1685 Accuracy_LoadColors();
1687 float oldposx = pos.x;
1691 FOREACH(Weapons, it != WEP_Null, {
1692 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1694 WepSet set = it.m_wepset;
1695 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1697 if (it.spawnflags & WEP_TYPE_OTHER)
1701 if (weapon_stats >= 0)
1702 weapon_alpha = sbt_fg_alpha;
1704 weapon_alpha = 0.2 * sbt_fg_alpha;
1707 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1709 if (weapon_stats >= 0) {
1710 weapons_with_stats += 1;
1711 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1713 string s = sprintf("%d%%", weapon_stats * 100);
1714 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1716 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1717 rgb = Accuracy_GetColor(weapon_stats);
1719 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1721 tmpos.x += weapon_width * rows;
1722 pos.x += weapon_width * rows;
1723 if (rows == 2 && column == columns - 1) {
1731 if (weapons_with_stats)
1732 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1734 panel_size.x += panel_bg_padding * 2; // restore initial width
1739 bool is_item_filtered(entity it)
1741 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1743 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1746 if (it.instanceOfArmor || it.instanceOfHealth)
1748 int ha_mask = floor(mask) % 10;
1751 default: return false;
1752 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1753 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1754 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1755 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1758 if (it.instanceOfAmmo)
1760 int ammo_mask = floor(mask / 10) % 10;
1761 return (ammo_mask == 1);
1766 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1768 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1770 int disowned_cnt = 0;
1771 int uninteresting_cnt = 0;
1772 IL_EACH(default_order_items, true, {
1773 int q = g_inventory.inv_items[it.m_id];
1774 //q = 1; // debug: display all items
1775 if (is_item_filtered(it))
1776 ++uninteresting_cnt;
1780 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1781 int n = items_cnt - disowned_cnt;
1782 if (n <= 0) return pos;
1784 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1785 int columns = max(6, ceil(n / rows));
1787 float item_height = hud_fontsize.y * 2.3;
1788 float height = item_height + hud_fontsize.y;
1790 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1791 pos.y += 1.25 * hud_fontsize.y;
1792 if(panel.current_panel_bg != "0")
1793 pos.y += panel_bg_border;
1796 panel_size.y = height * rows;
1797 panel_size.y += panel_bg_padding * 2;
1799 float panel_bg_alpha_save = panel_bg_alpha;
1800 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1802 panel_bg_alpha = panel_bg_alpha_save;
1804 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1805 if(panel.current_panel_bg != "0")
1806 end_pos.y += panel_bg_border * 2;
1808 if(panel_bg_padding)
1810 panel_pos += '1 1 0' * panel_bg_padding;
1811 panel_size -= '2 2 0' * panel_bg_padding;
1815 vector tmp = panel_size;
1817 float item_width = tmp.x / columns / rows;
1820 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1824 // column highlighting
1825 for (int i = 0; i < columns; ++i)
1827 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);
1830 for (int i = 0; i < rows; ++i)
1831 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1835 pos.x += item_width / 2;
1837 float oldposx = pos.x;
1841 IL_EACH(default_order_items, !is_item_filtered(it), {
1842 int n = g_inventory.inv_items[it.m_id];
1843 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1844 if (n <= 0) continue;
1845 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);
1847 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1848 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1849 tmpos.x += item_width * rows;
1850 pos.x += item_width * rows;
1851 if (rows == 2 && column == columns - 1) {
1859 panel_size.x += panel_bg_padding * 2; // restore initial width
1864 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1866 pos.x += hud_fontsize.x * 0.25;
1867 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1868 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1869 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1871 pos.y += hud_fontsize.y;
1876 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1877 float stat_secrets_found, stat_secrets_total;
1878 float stat_monsters_killed, stat_monsters_total;
1882 // get monster stats
1883 stat_monsters_killed = STAT(MONSTERS_KILLED);
1884 stat_monsters_total = STAT(MONSTERS_TOTAL);
1886 // get secrets stats
1887 stat_secrets_found = STAT(SECRETS_FOUND);
1888 stat_secrets_total = STAT(SECRETS_TOTAL);
1890 // get number of rows
1891 if(stat_secrets_total)
1893 if(stat_monsters_total)
1896 // if no rows, return
1900 // draw table header
1901 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1902 pos.y += 1.25 * hud_fontsize.y;
1903 if(panel.current_panel_bg != "0")
1904 pos.y += panel_bg_border;
1907 panel_size.y = hud_fontsize.y * rows;
1908 panel_size.y += panel_bg_padding * 2;
1911 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1912 if(panel.current_panel_bg != "0")
1913 end_pos.y += panel_bg_border * 2;
1915 if(panel_bg_padding)
1917 panel_pos += '1 1 0' * panel_bg_padding;
1918 panel_size -= '2 2 0' * panel_bg_padding;
1922 vector tmp = panel_size;
1925 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1928 if(stat_monsters_total)
1930 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1931 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1935 if(stat_secrets_total)
1937 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1938 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1941 panel_size.x += panel_bg_padding * 2; // restore initial width
1945 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1948 RANKINGS_RECEIVED_CNT = 0;
1949 for (i=RANKINGS_CNT-1; i>=0; --i)
1951 ++RANKINGS_RECEIVED_CNT;
1953 if (RANKINGS_RECEIVED_CNT == 0)
1956 vector hl_rgb = rgb + '0.5 0.5 0.5';
1958 vector scoreboard_selected_hl_pos = pos;
1960 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1961 pos.y += 1.25 * hud_fontsize.y;
1962 if(panel.current_panel_bg != "0")
1963 pos.y += panel_bg_border;
1965 vector scoreboard_selected_hl_size = '0 0 0';
1966 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1967 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1972 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1974 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1979 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1981 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1985 float ranksize = 3 * hud_fontsize.x;
1986 float timesize = 5 * hud_fontsize.x;
1987 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1988 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1989 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1992 rankings_cnt = RANKINGS_RECEIVED_CNT;
1993 rankings_rows = ceil(rankings_cnt / rankings_columns);
1996 // expand name column to fill the entire row
1997 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1998 namesize += available_space;
1999 columnsize.x += available_space;
2001 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2002 panel_size.y += panel_bg_padding * 2;
2003 scoreboard_selected_hl_size.y += panel_size.y;
2007 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2008 if(panel.current_panel_bg != "0")
2009 end_pos.y += panel_bg_border * 2;
2011 if(panel_bg_padding)
2013 panel_pos += '1 1 0' * panel_bg_padding;
2014 panel_size -= '2 2 0' * panel_bg_padding;
2020 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2022 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2024 int column = 0, j = 0;
2025 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2026 int start_item = rankings_start_column * rankings_rows;
2027 for(i = start_item; i < start_item + rankings_cnt; ++i)
2029 int t = grecordtime[i];
2033 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2034 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2035 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2036 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2038 str = count_ordinal(i+1);
2039 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2040 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2041 str = ColorTranslateRGB(grecordholder[i]);
2043 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2044 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2046 pos.y += 1.25 * hud_fontsize.y;
2048 if(j >= rankings_rows)
2052 pos.x += panel_size.x / rankings_columns;
2053 pos.y = panel_pos.y;
2056 strfree(zoned_name_self);
2058 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2060 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2061 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2064 panel_size.x += panel_bg_padding * 2; // restore initial width
2068 bool have_weapon_stats;
2069 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2071 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2073 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2076 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2077 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2083 if (!have_weapon_stats)
2085 FOREACH(Weapons, it != WEP_Null, {
2086 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2087 if (weapon_stats >= 0)
2089 have_weapon_stats = true;
2093 if (!have_weapon_stats)
2100 bool have_item_stats;
2101 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2103 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2105 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2108 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2109 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2115 if (!have_item_stats)
2117 IL_EACH(default_order_items, true, {
2118 if (!is_item_filtered(it))
2120 int q = g_inventory.inv_items[it.m_id];
2121 //q = 1; // debug: display all items
2124 have_item_stats = true;
2129 if (!have_item_stats)
2136 vector Scoreboard_Spectators_Draw(vector pos) {
2141 for(pl = players.sort_next; pl; pl = pl.sort_next)
2143 if(pl.team == NUM_SPECTATOR)
2145 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2146 if(tm.team == NUM_SPECTATOR)
2148 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2149 draw_beginBoldFont();
2150 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2152 pos.y += 1.25 * hud_fontsize.y;
2154 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2155 pos.y += 1.25 * hud_fontsize.y;
2160 if (str != "") // if there's at least one spectator
2161 pos.y += 0.5 * hud_fontsize.y;
2166 void Scoreboard_Draw()
2168 if(!autocvar__hud_configure)
2170 if(!hud_draw_maximized) return;
2172 // frametime checks allow to toggle the scoreboard even when the game is paused
2173 if(scoreboard_active) {
2174 if (scoreboard_fade_alpha == 0)
2175 scoreboard_time = time;
2176 if(hud_configure_menu_open == 1)
2177 scoreboard_fade_alpha = 1;
2178 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2179 if (scoreboard_fadeinspeed && frametime)
2180 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2182 scoreboard_fade_alpha = 1;
2183 if(hud_fontsize_str != autocvar_hud_fontsize)
2185 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2186 Scoreboard_initFieldSizes();
2187 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2191 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2192 if (scoreboard_fadeoutspeed && frametime)
2193 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2195 scoreboard_fade_alpha = 0;
2198 if (!scoreboard_fade_alpha)
2200 scoreboard_acc_fade_alpha = 0;
2201 scoreboard_itemstats_fade_alpha = 0;
2206 scoreboard_fade_alpha = 0;
2208 if (autocvar_hud_panel_scoreboard_dynamichud)
2211 HUD_Scale_Disable();
2213 if(scoreboard_fade_alpha <= 0)
2215 panel_fade_alpha *= scoreboard_fade_alpha;
2216 HUD_Panel_LoadCvars();
2218 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2219 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2220 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2221 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2222 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2223 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2224 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2226 // don't overlap with con_notify
2227 if(!autocvar__hud_configure)
2228 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2230 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2231 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2232 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2233 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2234 panel_pos.x = scoreboard_left;
2235 panel_size.x = fixed_scoreboard_width;
2237 Scoreboard_UpdatePlayerTeams();
2239 scoreboard_top = panel_pos.y;
2240 vector pos = panel_pos;
2245 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2247 // Begin of Game Info Section
2248 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2249 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2251 // Game Info: Game Type
2252 if (scoreboard_ui_enabled == 2)
2253 str = _("Team Selection");
2255 str = MapInfo_Type_ToText(gametype);
2256 draw_beginBoldFont();
2257 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);
2260 pos.y += sb_gameinfo_type_fontsize.y;
2261 // Game Info: Game Detail
2262 if (scoreboard_ui_enabled == 2)
2264 if (scoreboard_selected_team)
2265 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2267 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2268 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);
2270 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2271 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2272 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);
2276 float tl = STAT(TIMELIMIT);
2277 float fl = STAT(FRAGLIMIT);
2278 float ll = STAT(LEADLIMIT);
2279 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2282 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2283 if(!gametype.m_hidelimits)
2288 str = strcat(str, "^7 / "); // delimiter
2291 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2292 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2293 (teamscores_label(ts_primary) == "fastest") ? "" :
2294 TranslateScoresLabel(teamscores_label(ts_primary))));
2298 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2299 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2300 (scores_label(ps_primary) == "fastest") ? "" :
2301 TranslateScoresLabel(scores_label(ps_primary))));
2306 if(tl > 0 || fl > 0)
2309 if (ll_and_fl && fl > 0)
2310 str = strcat(str, "^7 & ");
2312 str = strcat(str, "^7 / ");
2317 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2318 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2319 (teamscores_label(ts_primary) == "fastest") ? "" :
2320 TranslateScoresLabel(teamscores_label(ts_primary))));
2324 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2325 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2326 (scores_label(ps_primary) == "fastest") ? "" :
2327 TranslateScoresLabel(scores_label(ps_primary))));
2331 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
2333 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2334 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2336 // End of Game Info Section
2338 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2339 if(panel.current_panel_bg != "0")
2340 pos.y += panel_bg_border;
2342 // Draw the scoreboard
2343 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2346 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2350 vector panel_bg_color_save = panel_bg_color;
2351 vector team_score_baseoffset;
2352 vector team_size_baseoffset;
2353 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2355 // put team score to the left of scoreboard (and team size to the right)
2356 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2357 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2358 if(panel.current_panel_bg != "0")
2360 team_score_baseoffset.x -= panel_bg_border;
2361 team_size_baseoffset.x += panel_bg_border;
2366 // put team score to the right of scoreboard (and team size to the left)
2367 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2368 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2369 if(panel.current_panel_bg != "0")
2371 team_score_baseoffset.x += panel_bg_border;
2372 team_size_baseoffset.x -= panel_bg_border;
2376 int team_size_total = 0;
2377 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2379 // calculate team size total (sum of all team sizes)
2380 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2381 if(tm.team != NUM_SPECTATOR)
2382 team_size_total += tm.team_size;
2385 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2387 if(tm.team == NUM_SPECTATOR)
2392 draw_beginBoldFont();
2393 vector rgb = Team_ColorRGB(tm.team);
2394 str = ftos(tm.(teamscores(ts_primary)));
2395 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2397 // team score on the left (default)
2398 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2402 // team score on the right
2403 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2405 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2407 // team size (if set to show on the side)
2408 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2410 // calculate the starting position for the whole team size info string
2411 str = sprintf("%d/%d", tm.team_size, team_size_total);
2412 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2414 // team size on the left
2415 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2419 // team size on the right
2420 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2422 str = sprintf("%d", tm.team_size);
2423 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2424 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2425 str = sprintf("/%d", team_size_total);
2426 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2430 // secondary score, e.g. keyhunt
2431 if(ts_primary != ts_secondary)
2433 str = ftos(tm.(teamscores(ts_secondary)));
2434 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2437 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2442 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2445 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2448 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2449 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2450 else if(panel_bg_color_team > 0)
2451 panel_bg_color = rgb * panel_bg_color_team;
2453 panel_bg_color = rgb;
2454 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2456 panel_bg_color = panel_bg_color_save;
2460 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2461 if(tm.team != NUM_SPECTATOR)
2464 // display it anyway
2465 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2468 // draw scoreboard spectators before accuracy and item stats
2469 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2470 pos = Scoreboard_Spectators_Draw(pos);
2473 // draw accuracy and item stats
2474 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2475 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2476 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2477 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2479 // draw scoreboard spectators after accuracy and item stats and before rankings
2480 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2481 pos = Scoreboard_Spectators_Draw(pos);
2484 if(MUTATOR_CALLHOOK(ShowRankings)) {
2485 string ranktitle = M_ARGV(0, string);
2486 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2487 if(race_speedaward_alltimebest)
2490 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2494 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2495 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2496 str = strcat(str, " / ");
2498 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2499 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2500 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2501 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2503 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2508 // draw scoreboard spectators after rankings
2509 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2510 pos = Scoreboard_Spectators_Draw(pos);
2513 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2515 // draw scoreboard spectators after mapstats
2516 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2517 pos = Scoreboard_Spectators_Draw(pos);
2521 // print information about respawn status
2522 float respawn_time = STAT(RESPAWN_TIME);
2523 if(!intermission && respawn_time)
2525 if(respawn_time < 0)
2527 // a negative number means we are awaiting respawn, time value is still the same
2528 respawn_time *= -1; // remove mark now that we checked it
2530 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2531 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2533 str = sprintf(_("^1Respawning in ^3%s^1..."),
2534 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2535 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2537 count_seconds(ceil(respawn_time - time))
2541 else if(time < respawn_time)
2543 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2544 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2545 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2547 count_seconds(ceil(respawn_time - time))
2551 else if(time >= respawn_time)
2552 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2554 pos.y += 1.2 * hud_fontsize.y;
2555 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2558 pos.y += hud_fontsize.y;
2559 if (scoreboard_fade_alpha < 1)
2560 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2561 else if (pos.y != scoreboard_bottom)
2563 if (pos.y > scoreboard_bottom)
2564 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2566 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2571 if (scoreboard_fade_alpha == 1)
2573 if (scoreboard_bottom > 0.95 * vid_conheight)
2574 rankings_rows = max(1, rankings_rows - 1);
2575 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2576 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2578 rankings_cnt = rankings_rows * rankings_columns;