1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 float autocvar_hud_panel_scoreboard_spectators_position = 1;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_itemstats = true;
93 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
94 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
95 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
96 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
99 bool autocvar_hud_panel_scoreboard_dynamichud = false;
101 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
102 bool autocvar_hud_panel_scoreboard_others_showscore = true;
103 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
104 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
105 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
106 bool autocvar_hud_panel_scoreboard_playerid = false;
107 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
108 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
110 float scoreboard_time;
112 // mode 0: returns translated label
113 // mode 1: prints name and description of all the labels
114 string Label_getInfo(string label, int mode)
117 label = "bckills"; // first case in the switch
121 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
122 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
123 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
124 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
125 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
126 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
127 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
128 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
129 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
130 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
131 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
132 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
133 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
134 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
135 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
136 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
137 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
138 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
139 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
140 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
141 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
142 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
143 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
144 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
145 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
146 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
147 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
148 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
149 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
150 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
151 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
152 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
153 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
154 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
155 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
156 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
157 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
158 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
159 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
160 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
161 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
162 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
163 default: return label;
168 bool scoreboard_ui_disabling;
169 void HUD_Scoreboard_UI_Disable()
171 scoreboard_ui_disabling = true;
172 scoreboard_showscores = false;
175 void HUD_Scoreboard_UI_Disable_Instantly()
177 scoreboard_ui_disabling = false;
178 scoreboard_ui_enabled = 0;
179 scoreboard_selected_panel = 0;
180 scoreboard_selected_player = NULL;
181 scoreboard_selected_team = NULL;
184 // mode: 0 normal, 1 team selection
185 void Scoreboard_UI_Enable(int mode)
191 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
194 // release player's pressed keys as they aren't released elsewhere
195 // in particular jump needs to be released as it may open the team selection
196 // (when server detects jump has been pressed it sends the command to open the team selection)
197 Release_Common_Keys();
198 scoreboard_ui_enabled = 2;
199 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
203 if (scoreboard_ui_enabled == 1)
205 scoreboard_ui_enabled = 1;
206 scoreboard_selected_panel = SB_PANEL_FIRST;
208 scoreboard_selected_player = NULL;
209 scoreboard_selected_team = NULL;
210 scoreboard_selected_panel_time = time;
213 int rankings_start_column;
214 int rankings_rows = 0;
215 int rankings_columns = 0;
216 int rankings_cnt = 0;
217 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
221 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
226 mousepos.x = nPrimary;
227 mousepos.y = nSecondary;
234 // at this point bInputType can only be 0 or 1 (key pressed or released)
235 bool key_pressed = (bInputType == 0);
237 // ESC to exit (TAB-ESC works too)
238 if(nPrimary == K_ESCAPE)
242 HUD_Scoreboard_UI_Disable();
246 // block any input while a menu dialog is fading
247 if(autocvar__menu_alpha)
253 // allow console bind to work
254 string con_keys = findkeysforcommand("toggleconsole", 0);
255 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
257 bool hit_con_bind = false;
259 for (i = 0; i < keys; ++i)
261 if(nPrimary == stof(argv(i)))
266 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
267 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
268 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
269 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
272 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
273 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
274 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
275 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
278 if(nPrimary == K_TAB)
282 if (scoreboard_ui_enabled == 2)
284 if (hudShiftState & S_SHIFT)
287 goto downarrow_action;
290 if (hudShiftState & S_SHIFT)
292 --scoreboard_selected_panel;
293 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
294 --scoreboard_selected_panel;
295 if (scoreboard_selected_panel < SB_PANEL_FIRST)
296 scoreboard_selected_panel = SB_PANEL_MAX;
300 ++scoreboard_selected_panel;
301 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
302 ++scoreboard_selected_panel;
303 if (scoreboard_selected_panel > SB_PANEL_MAX)
304 scoreboard_selected_panel = SB_PANEL_FIRST;
307 scoreboard_selected_panel_time = time;
309 else if(nPrimary == K_DOWNARROW)
313 LABEL(downarrow_action);
314 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
316 if (scoreboard_ui_enabled == 2)
318 entity curr_team = NULL;
319 bool scoreboard_selected_team_found = false;
320 if (!scoreboard_selected_team)
321 scoreboard_selected_team_found = true;
323 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
325 if(tm.team == NUM_SPECTATOR)
328 if (scoreboard_selected_team_found)
330 if (scoreboard_selected_team == tm)
331 scoreboard_selected_team_found = true;
334 if (curr_team == scoreboard_selected_team) // loop reached the last team
336 scoreboard_selected_team = curr_team;
341 entity curr_pl = NULL;
342 bool scoreboard_selected_player_found = false;
343 if (!scoreboard_selected_player)
344 scoreboard_selected_player_found = true;
346 for(tm = teams.sort_next; tm; tm = tm.sort_next)
348 if(tm.team != NUM_SPECTATOR)
349 for(pl = players.sort_next; pl; pl = pl.sort_next)
351 if(pl.team != tm.team)
354 if (scoreboard_selected_player_found)
356 if (scoreboard_selected_player == pl)
357 scoreboard_selected_player_found = true;
361 if (curr_pl == scoreboard_selected_player) // loop reached the last player
363 scoreboard_selected_player = curr_pl;
367 else if(nPrimary == K_UPARROW)
371 LABEL(uparrow_action);
372 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
374 if (scoreboard_ui_enabled == 2)
376 entity prev_team = NULL;
377 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
379 if(tm.team == NUM_SPECTATOR)
381 if (tm == scoreboard_selected_team)
386 scoreboard_selected_team = prev_team;
390 entity prev_pl = NULL;
392 for(tm = teams.sort_next; tm; tm = tm.sort_next)
394 if(tm.team != NUM_SPECTATOR)
395 for(pl = players.sort_next; pl; pl = pl.sort_next)
397 if(pl.team != tm.team)
399 if (pl == scoreboard_selected_player)
405 scoreboard_selected_player = prev_pl;
409 else if(nPrimary == K_RIGHTARROW)
413 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
414 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
416 else if(nPrimary == K_LEFTARROW)
420 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
421 rankings_start_column = max(rankings_start_column - 1, 0);
423 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
427 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
429 if (scoreboard_ui_enabled == 2)
432 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
435 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
436 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
437 HUD_Scoreboard_UI_Disable();
439 else if (scoreboard_selected_player)
440 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
443 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
447 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
449 switch (scoreboard_selected_columns_layout)
452 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
454 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
455 scoreboard_selected_columns_layout = 1;
460 localcmd(sprintf("scoreboard_columns_set default\n"));
461 scoreboard_selected_columns_layout = 2;
464 localcmd(sprintf("scoreboard_columns_set all\n"));
465 scoreboard_selected_columns_layout = 0;
470 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
474 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
476 if (scoreboard_selected_player)
478 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
479 HUD_Scoreboard_UI_Disable();
483 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
487 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
489 if (scoreboard_selected_player)
490 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
493 else if(hit_con_bind || nPrimary == K_PAUSE)
499 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
500 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
502 #define SB_EXTRA_SORTING_FIELDS 5
503 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
504 void Scoreboard_InitScores()
508 ps_primary = ps_secondary = NULL;
509 ts_primary = ts_secondary = -1;
510 FOREACH(Scores, true, {
511 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
512 if(f == SFL_SORT_PRIO_PRIMARY)
514 if(f == SFL_SORT_PRIO_SECONDARY)
516 if(ps_primary == it || ps_secondary == it)
518 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
519 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
520 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
521 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
522 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
524 if(ps_secondary == NULL)
525 ps_secondary = ps_primary;
527 for(i = 0; i < MAX_TEAMSCORE; ++i)
529 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
530 if(f == SFL_SORT_PRIO_PRIMARY)
532 if(f == SFL_SORT_PRIO_SECONDARY)
535 if(ts_secondary == -1)
536 ts_secondary = ts_primary;
538 Cmd_Scoreboard_SetFields(0);
542 void Scoreboard_UpdatePlayerTeams()
544 static float update_time;
545 if (time <= update_time)
551 for(pl = players.sort_next; pl; pl = pl.sort_next)
554 int Team = entcs_GetScoreTeam(pl.sv_entnum);
555 if(SetTeam(pl, Team))
558 Scoreboard_UpdatePlayerPos(pl);
562 pl = players.sort_next;
567 print(strcat("PNUM: ", ftos(num), "\n"));
572 int Scoreboard_CompareScore(int vl, int vr, int f)
574 TC(int, vl); TC(int, vr); TC(int, f);
575 if(f & SFL_ZERO_IS_WORST)
577 if(vl == 0 && vr != 0)
579 if(vl != 0 && vr == 0)
583 return IS_INCREASING(f);
585 return IS_DECREASING(f);
589 float Scoreboard_ComparePlayerScores(entity left, entity right)
591 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
592 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
599 if(vl == NUM_SPECTATOR)
601 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
603 if(!left.gotscores && right.gotscores)
610 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
614 if (!fld) fld = ps_primary;
615 else if (ps_secondary == ps_primary) continue;
616 else fld = ps_secondary;
620 fld = sb_extra_sorting_field[i];
621 if (fld == ps_primary || fld == ps_secondary) continue;
625 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
626 if (r >= 0) return r;
629 if (left.sv_entnum < right.sv_entnum)
635 void Scoreboard_UpdatePlayerPos(entity player)
638 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
640 SORT_SWAP(player, ent);
642 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
644 SORT_SWAP(ent, player);
648 float Scoreboard_CompareTeamScores(entity left, entity right)
650 if(left.team == NUM_SPECTATOR)
652 if(right.team == NUM_SPECTATOR)
657 for(int i = -2; i < MAX_TEAMSCORE; ++i)
661 if (fld_idx == -1) fld_idx = ts_primary;
662 else if (ts_secondary == ts_primary) continue;
663 else fld_idx = ts_secondary;
668 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
671 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
672 if (r >= 0) return r;
675 if (left.team < right.team)
681 void Scoreboard_UpdateTeamPos(entity Team)
684 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
686 SORT_SWAP(Team, ent);
688 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
690 SORT_SWAP(ent, Team);
694 void Cmd_Scoreboard_Help()
696 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
697 LOG_HELP(_("Usage:"));
698 LOG_HELP("^2scoreboard_columns_set ^3default");
699 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
700 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
701 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
702 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
703 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
704 LOG_HELP(_("The following field names are recognized (case insensitive):"));
710 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
711 "of game types, then a slash, to make the field show up only in these\n"
712 "or in all but these game types. You can also specify 'all' as a\n"
713 "field to show all fields available for the current game mode."));
716 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
717 "include/exclude ALL teams/noteams game modes."));
720 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
721 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
722 "right of the vertical bar aligned to the right."));
723 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
724 "other gamemodes except DM."));
727 // NOTE: adding a gametype with ? to not warn for an optional field
728 // make sure it's excluded in a previous exclusive rule, if any
729 // otherwise the previous exclusive rule warns anyway
730 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
731 #define SCOREBOARD_DEFAULT_COLUMNS \
732 "ping pl fps name |" \
733 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
734 " -teams,lms/deaths +ft,tdm/deaths" \
736 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
737 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
738 " +tdm,ft,dom,ons,as/teamkills"\
739 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
740 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
741 " +lms/lives +lms/rank" \
742 " +kh/kckills +kh/losses +kh/caps" \
743 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
744 " +as/objectives +nb/faults +nb/goals" \
745 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
746 " +dom/ticks +dom/takes" \
747 " -lms,rc,cts,inv,nb/score"
749 void Cmd_Scoreboard_SetFields(int argc)
754 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
758 return; // do nothing, we don't know gametype and scores yet
760 // sbt_fields uses strunzone on the titles!
761 if(!sbt_field_title[0])
762 for(i = 0; i < MAX_SBT_FIELDS; ++i)
763 sbt_field_title[i] = strzone("(null)");
765 // TODO: re enable with gametype dependant cvars?
766 if(argc < 3) // no arguments provided
767 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
770 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
774 if(argv(2) == "default" || argv(2) == "expand_default")
776 if(argv(2) == "expand_default")
777 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
778 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
780 else if(argv(2) == "all" || argv(2) == "ALL")
782 string s = "ping pl name |"; // scores without label (not really scores)
785 // scores without label
786 s = strcat(s, " ", "sum");
787 s = strcat(s, " ", "kdratio");
788 s = strcat(s, " ", "frags");
790 FOREACH(Scores, true, {
792 if(it != ps_secondary)
793 if(scores_label(it) != "")
794 s = strcat(s, " ", scores_label(it));
796 if(ps_secondary != ps_primary)
797 s = strcat(s, " ", scores_label(ps_secondary));
798 s = strcat(s, " ", scores_label(ps_primary));
799 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
806 hud_fontsize = HUD_GetFontsize("hud_fontsize");
808 for(i = 1; i < argc - 1; ++i)
811 bool nocomplain = false;
812 if(substring(str, 0, 1) == "?")
815 str = substring(str, 1, strlen(str) - 1);
818 slash = strstrofs(str, "/", 0);
821 pattern = substring(str, 0, slash);
822 str = substring(str, slash + 1, strlen(str) - (slash + 1));
824 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
828 str = strtolower(str);
829 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
830 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
835 // fields without a label (not networked via the score system)
836 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
837 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
838 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
839 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
840 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
841 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
842 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
843 default: // fields with a label
845 // map alternative labels
846 if (str == "damage") str = "dmg";
847 if (str == "damagetaken") str = "dmgtaken";
849 FOREACH(Scores, true, {
850 if (str == strtolower(scores_label(it))) {
852 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
856 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
857 if(!nocomplain && str != "fps") // server can disable the fps field
858 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
860 strfree(sbt_field_title[sbt_num_fields]);
861 sbt_field_size[sbt_num_fields] = 0;
865 sbt_field[sbt_num_fields] = j;
868 if(j == ps_secondary)
869 have_secondary = true;
874 if(sbt_num_fields >= MAX_SBT_FIELDS)
878 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
880 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
881 have_secondary = true;
882 if(ps_primary == ps_secondary)
883 have_secondary = true;
884 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
886 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
890 strfree(sbt_field_title[sbt_num_fields]);
891 for(i = sbt_num_fields; i > 0; --i)
893 sbt_field_title[i] = sbt_field_title[i-1];
894 sbt_field_size[i] = sbt_field_size[i-1];
895 sbt_field[i] = sbt_field[i-1];
897 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
898 sbt_field[0] = SP_NAME;
900 LOG_INFO("fixed missing field 'name'");
904 strfree(sbt_field_title[sbt_num_fields]);
905 for(i = sbt_num_fields; i > 1; --i)
907 sbt_field_title[i] = sbt_field_title[i-1];
908 sbt_field_size[i] = sbt_field_size[i-1];
909 sbt_field[i] = sbt_field[i-1];
911 sbt_field_title[1] = strzone("|");
912 sbt_field[1] = SP_SEPARATOR;
913 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
915 LOG_INFO("fixed missing field '|'");
918 else if(!have_separator)
920 strcpy(sbt_field_title[sbt_num_fields], "|");
921 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
922 sbt_field[sbt_num_fields] = SP_SEPARATOR;
924 LOG_INFO("fixed missing field '|'");
928 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
929 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
930 sbt_field[sbt_num_fields] = ps_secondary;
932 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
936 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
937 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
938 sbt_field[sbt_num_fields] = ps_primary;
940 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
944 sbt_field[sbt_num_fields] = SP_END;
947 string Scoreboard_AddPlayerId(string pl_name, entity pl)
949 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
950 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
951 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
955 vector sbt_field_rgb;
956 string sbt_field_icon0;
957 string sbt_field_icon1;
958 string sbt_field_icon2;
959 vector sbt_field_icon0_rgb;
960 vector sbt_field_icon1_rgb;
961 vector sbt_field_icon2_rgb;
962 string Scoreboard_GetName(entity pl)
964 if(ready_waiting && pl.ready)
966 sbt_field_icon0 = "gfx/scoreboard/player_ready";
970 int f = entcs_GetClientColors(pl.sv_entnum);
972 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
973 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
974 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
975 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
976 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
979 return entcs_GetName(pl.sv_entnum);
982 string Scoreboard_GetField(entity pl, PlayerScoreField field)
984 float tmp, num, denom;
987 sbt_field_rgb = '1 1 1';
988 sbt_field_icon0 = "";
989 sbt_field_icon1 = "";
990 sbt_field_icon2 = "";
991 sbt_field_icon0_rgb = '1 1 1';
992 sbt_field_icon1_rgb = '1 1 1';
993 sbt_field_icon2_rgb = '1 1 1';
998 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
999 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1003 tmp = max(0, min(220, f-80)) / 220;
1004 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1010 f = pl.ping_packetloss;
1011 tmp = pl.ping_movementloss;
1012 if(f == 0 && tmp == 0)
1014 str = ftos(ceil(f * 100));
1016 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1017 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1018 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1022 str = Scoreboard_GetName(pl);
1023 if (autocvar_hud_panel_scoreboard_playerid)
1024 str = Scoreboard_AddPlayerId(str, pl);
1028 f = pl.(scores(SP_KILLS));
1029 f -= pl.(scores(SP_SUICIDES));
1033 num = pl.(scores(SP_KILLS));
1034 denom = pl.(scores(SP_DEATHS));
1037 sbt_field_rgb = '0 1 0';
1038 str = sprintf("%d", num);
1039 } else if(num <= 0) {
1040 sbt_field_rgb = '1 0 0';
1041 str = sprintf("%.1f", num/denom);
1043 str = sprintf("%.1f", num/denom);
1047 f = pl.(scores(SP_KILLS));
1048 f -= pl.(scores(SP_DEATHS));
1051 sbt_field_rgb = '0 1 0';
1053 sbt_field_rgb = '1 1 1';
1055 sbt_field_rgb = '1 0 0';
1061 float elo = pl.(scores(SP_ELO));
1063 case -1: return "...";
1064 case -2: return _("N/A");
1065 default: return ftos(elo);
1071 float fps = pl.(scores(SP_FPS));
1074 sbt_field_rgb = '1 1 1';
1075 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1077 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1078 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1082 case SP_DMG: case SP_DMGTAKEN:
1083 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1085 default: case SP_SCORE:
1086 tmp = pl.(scores(field));
1087 f = scores_flags(field);
1088 if(field == ps_primary)
1089 sbt_field_rgb = '1 1 0';
1090 else if(field == ps_secondary)
1091 sbt_field_rgb = '0 1 1';
1093 sbt_field_rgb = '1 1 1';
1094 return ScoreString(f, tmp);
1099 float sbt_fixcolumnwidth_len;
1100 float sbt_fixcolumnwidth_iconlen;
1101 float sbt_fixcolumnwidth_marginlen;
1103 string Scoreboard_FixColumnWidth(int i, string str)
1109 sbt_fixcolumnwidth_iconlen = 0;
1111 if(sbt_field_icon0 != "")
1113 sz = draw_getimagesize(sbt_field_icon0);
1115 if(sbt_fixcolumnwidth_iconlen < f)
1116 sbt_fixcolumnwidth_iconlen = f;
1119 if(sbt_field_icon1 != "")
1121 sz = draw_getimagesize(sbt_field_icon1);
1123 if(sbt_fixcolumnwidth_iconlen < f)
1124 sbt_fixcolumnwidth_iconlen = f;
1127 if(sbt_field_icon2 != "")
1129 sz = draw_getimagesize(sbt_field_icon2);
1131 if(sbt_fixcolumnwidth_iconlen < f)
1132 sbt_fixcolumnwidth_iconlen = f;
1135 if(sbt_fixcolumnwidth_iconlen != 0)
1137 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1138 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1141 sbt_fixcolumnwidth_marginlen = 0;
1143 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1146 float remaining_space = 0;
1147 for(j = 0; j < sbt_num_fields; ++j)
1149 if (sbt_field[i] != SP_SEPARATOR)
1150 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1151 sbt_field_size[i] = panel_size.x - remaining_space;
1153 if (sbt_fixcolumnwidth_iconlen != 0)
1154 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1155 float namesize = panel_size.x - remaining_space;
1156 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1157 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1159 max_namesize = vid_conwidth - remaining_space;
1162 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1164 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1165 if(sbt_field_size[i] < f)
1166 sbt_field_size[i] = f;
1171 void Scoreboard_initFieldSizes()
1173 for(int i = 0; i < sbt_num_fields; ++i)
1175 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1176 Scoreboard_FixColumnWidth(i, "");
1180 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1183 vector column_dim = eY * panel_size.y;
1185 column_dim.y -= 1.25 * hud_fontsize.y;
1186 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1187 pos.x += hud_fontsize.x * 0.5;
1188 for(i = 0; i < sbt_num_fields; ++i)
1190 if(sbt_field[i] == SP_SEPARATOR)
1192 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1195 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1196 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1197 pos.x += column_dim.x;
1199 if(sbt_field[i] == SP_SEPARATOR)
1201 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1202 for(i = sbt_num_fields - 1; i > 0; --i)
1204 if(sbt_field[i] == SP_SEPARATOR)
1207 pos.x -= sbt_field_size[i];
1212 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1213 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1216 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1217 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1218 pos.x -= hud_fontsize.x;
1222 pos.x = panel_pos.x;
1223 pos.y += 1.25 * hud_fontsize.y;
1227 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1229 TC(bool, is_self); TC(int, pl_number);
1231 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1233 vector h_pos = item_pos;
1234 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1235 // alternated rows highlighting
1236 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1238 if (pl == scoreboard_selected_player)
1239 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1242 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1243 else if((sbt_highlight) && (!(pl_number % 2)))
1244 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1246 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1248 vector pos = item_pos;
1249 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1251 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1253 pos.x += hud_fontsize.x * 0.5;
1254 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1255 vector tmp = '0 0 0';
1257 PlayerScoreField field;
1258 for(i = 0; i < sbt_num_fields; ++i)
1260 field = sbt_field[i];
1261 if(field == SP_SEPARATOR)
1264 if(is_spec && field != SP_NAME && field != SP_PING) {
1265 pos.x += sbt_field_size[i] + hud_fontsize.x;
1268 str = Scoreboard_GetField(pl, field);
1269 str = Scoreboard_FixColumnWidth(i, str);
1271 pos.x += sbt_field_size[i] + hud_fontsize.x;
1273 if(field == SP_NAME) {
1274 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1275 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1277 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1278 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1281 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1282 if(sbt_field_icon0 != "")
1283 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1284 if(sbt_field_icon1 != "")
1285 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1286 if(sbt_field_icon2 != "")
1287 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1290 if(sbt_field[i] == SP_SEPARATOR)
1292 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1293 for(i = sbt_num_fields-1; i > 0; --i)
1295 field = sbt_field[i];
1296 if(field == SP_SEPARATOR)
1299 if(is_spec && field != SP_NAME && field != SP_PING) {
1300 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1304 str = Scoreboard_GetField(pl, field);
1305 str = Scoreboard_FixColumnWidth(i, str);
1307 if(field == SP_NAME) {
1308 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1309 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1311 tmp.x = sbt_fixcolumnwidth_len;
1312 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1315 tmp.x = sbt_field_size[i];
1316 if(sbt_field_icon0 != "")
1317 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1318 if(sbt_field_icon1 != "")
1319 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1320 if(sbt_field_icon2 != "")
1321 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1322 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1327 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1330 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1333 vector h_pos = item_pos;
1334 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1336 bool complete = (this_team == NUM_SPECTATOR);
1339 if((sbt_highlight) && (!(pl_number % 2)))
1340 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1342 vector pos = item_pos;
1343 pos.x += hud_fontsize.x * 0.5;
1344 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1346 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1348 width_limit -= stringwidth("...", false, hud_fontsize);
1349 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1350 static float max_name_width = 0;
1352 float fieldsize = 0;
1353 float min_fieldsize = 0;
1354 float fieldpadding = hud_fontsize.x * 0.25;
1355 if(this_team == NUM_SPECTATOR)
1357 if(autocvar_hud_panel_scoreboard_spectators_showping)
1358 min_fieldsize = stringwidth("999", false, hud_fontsize);
1360 else if(autocvar_hud_panel_scoreboard_others_showscore)
1361 min_fieldsize = stringwidth("99", false, hud_fontsize);
1362 for(i = 0; pl; pl = pl.sort_next)
1364 if(pl.team != this_team)
1366 if(pl == ignored_pl)
1370 if(this_team == NUM_SPECTATOR)
1372 if(autocvar_hud_panel_scoreboard_spectators_showping)
1373 field = Scoreboard_GetField(pl, SP_PING);
1375 else if(autocvar_hud_panel_scoreboard_others_showscore)
1376 field = Scoreboard_GetField(pl, SP_SCORE);
1378 string str = entcs_GetName(pl.sv_entnum);
1379 if (autocvar_hud_panel_scoreboard_playerid)
1380 str = Scoreboard_AddPlayerId(str, pl);
1381 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1382 float column_width = stringwidth(str, true, hud_fontsize);
1383 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1385 if(column_width > max_name_width)
1386 max_name_width = column_width;
1387 column_width = max_name_width;
1391 fieldsize = stringwidth(field, false, hud_fontsize);
1392 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1395 if(pos.x + column_width > width_limit)
1400 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1405 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1406 pos.y += hud_fontsize.y * 1.25;
1410 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1412 if (pl == scoreboard_selected_player)
1414 h_size.x = column_width + hud_fontsize.x * 0.25;
1415 h_size.y = hud_fontsize.y;
1416 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1420 vector name_pos = pos;
1421 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1422 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1423 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1426 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1427 h_size.y = hud_fontsize.y;
1428 vector field_pos = pos;
1429 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1430 field_pos.x += column_width - h_size.x;
1432 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1433 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1434 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1438 h_size.x = column_width + hud_fontsize.x * 0.25;
1439 h_size.y = hud_fontsize.y;
1440 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1442 pos.x += column_width;
1443 pos.x += hud_fontsize.x;
1445 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1448 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1450 int max_players = 999;
1451 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1453 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1456 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1457 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1458 height /= team_count;
1461 height -= panel_bg_padding * 2; // - padding
1462 max_players = floor(height / (hud_fontsize.y * 1.25));
1463 if(max_players <= 1)
1465 if(max_players == tm.team_size)
1470 entity me = playerslots[current_player];
1472 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1473 panel_size.y += panel_bg_padding * 2;
1475 vector scoreboard_selected_hl_pos = pos;
1476 vector scoreboard_selected_hl_size = '0 0 0';
1477 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1478 scoreboard_selected_hl_size.y = panel_size.y;
1482 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1483 if(panel.current_panel_bg != "0")
1484 end_pos.y += panel_bg_border * 2;
1486 if(panel_bg_padding)
1488 panel_pos += '1 1 0' * panel_bg_padding;
1489 panel_size -= '2 2 0' * panel_bg_padding;
1493 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1497 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1499 pos.y += 1.25 * hud_fontsize.y;
1502 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1504 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1507 // print header row and highlight columns
1508 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1510 // fill the table and draw the rows
1511 bool is_self = false;
1512 bool self_shown = false;
1514 for(pl = players.sort_next; pl; pl = pl.sort_next)
1516 if(pl.team != tm.team)
1518 if(i == max_players - 2 && pl != me)
1520 if(!self_shown && me.team == tm.team)
1522 Scoreboard_DrawItem(pos, rgb, me, true, i);
1524 pos.y += 1.25 * hud_fontsize.y;
1528 if(i >= max_players - 1)
1530 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1533 is_self = (pl.sv_entnum == current_player);
1534 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1537 pos.y += 1.25 * hud_fontsize.y;
1541 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1543 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1545 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1546 _alpha *= panel_fg_alpha;
1548 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1552 panel_size.x += panel_bg_padding * 2; // restore initial width
1556 bool Scoreboard_WouldDraw()
1558 if (scoreboard_ui_enabled)
1560 if (scoreboard_ui_disabling)
1562 if (scoreboard_fade_alpha == 0)
1563 HUD_Scoreboard_UI_Disable_Instantly();
1566 if (intermission && scoreboard_ui_enabled == 2)
1568 HUD_Scoreboard_UI_Disable_Instantly();
1573 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1575 else if (QuickMenu_IsOpened())
1577 else if (HUD_Radar_Clickable())
1579 else if (scoreboard_showscores)
1581 else if (intermission == 1)
1583 else if (intermission == 2)
1585 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1586 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1590 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1595 float average_accuracy;
1596 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1598 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1600 WepSet weapons_stat = WepSet_GetFromStat();
1601 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1602 int disownedcnt = 0;
1604 FOREACH(Weapons, it != WEP_Null, {
1605 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1607 WepSet set = it.m_wepset;
1608 if(it.spawnflags & WEP_TYPE_OTHER)
1613 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1615 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1622 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1623 if (weapon_cnt <= 0) return pos;
1626 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1628 int columns = ceil(weapon_cnt / rows);
1630 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1631 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1632 float height = weapon_height + hud_fontsize.y;
1634 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);
1635 pos.y += 1.25 * hud_fontsize.y;
1636 if(panel.current_panel_bg != "0")
1637 pos.y += panel_bg_border;
1640 panel_size.y = height * rows;
1641 panel_size.y += panel_bg_padding * 2;
1643 float panel_bg_alpha_save = panel_bg_alpha;
1644 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1646 panel_bg_alpha = panel_bg_alpha_save;
1648 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1649 if(panel.current_panel_bg != "0")
1650 end_pos.y += panel_bg_border * 2;
1652 if(panel_bg_padding)
1654 panel_pos += '1 1 0' * panel_bg_padding;
1655 panel_size -= '2 2 0' * panel_bg_padding;
1659 vector tmp = panel_size;
1661 float weapon_width = tmp.x / columns / rows;
1664 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1668 // column highlighting
1669 for (int i = 0; i < columns; ++i)
1671 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);
1674 for (int i = 0; i < rows; ++i)
1675 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1678 average_accuracy = 0;
1679 int weapons_with_stats = 0;
1681 pos.x += weapon_width / 2;
1683 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1686 Accuracy_LoadColors();
1688 float oldposx = pos.x;
1692 FOREACH(Weapons, it != WEP_Null, {
1693 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1695 WepSet set = it.m_wepset;
1696 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1698 if (it.spawnflags & WEP_TYPE_OTHER)
1702 if (weapon_stats >= 0)
1703 weapon_alpha = sbt_fg_alpha;
1705 weapon_alpha = 0.2 * sbt_fg_alpha;
1708 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1710 if (weapon_stats >= 0) {
1711 weapons_with_stats += 1;
1712 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1714 string s = sprintf("%d%%", weapon_stats * 100);
1715 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1717 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1718 rgb = Accuracy_GetColor(weapon_stats);
1720 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1722 tmpos.x += weapon_width * rows;
1723 pos.x += weapon_width * rows;
1724 if (rows == 2 && column == columns - 1) {
1732 if (weapons_with_stats)
1733 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1735 panel_size.x += panel_bg_padding * 2; // restore initial width
1740 bool is_item_filtered(entity it)
1742 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1744 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1747 if (it.instanceOfArmor || it.instanceOfHealth)
1749 int ha_mask = floor(mask) % 10;
1752 default: return false;
1753 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1754 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1755 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1756 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1759 if (it.instanceOfAmmo)
1761 int ammo_mask = floor(mask / 10) % 10;
1762 return (ammo_mask == 1);
1767 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1769 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1771 int disowned_cnt = 0;
1772 int uninteresting_cnt = 0;
1773 IL_EACH(default_order_items, true, {
1774 int q = g_inventory.inv_items[it.m_id];
1775 //q = 1; // debug: display all items
1776 if (is_item_filtered(it))
1777 ++uninteresting_cnt;
1781 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1782 int n = items_cnt - disowned_cnt;
1783 if (n <= 0) return pos;
1785 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1786 int columns = max(6, ceil(n / rows));
1788 float item_height = hud_fontsize.y * 2.3;
1789 float height = item_height + hud_fontsize.y;
1791 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1792 pos.y += 1.25 * hud_fontsize.y;
1793 if(panel.current_panel_bg != "0")
1794 pos.y += panel_bg_border;
1797 panel_size.y = height * rows;
1798 panel_size.y += panel_bg_padding * 2;
1800 float panel_bg_alpha_save = panel_bg_alpha;
1801 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1803 panel_bg_alpha = panel_bg_alpha_save;
1805 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1806 if(panel.current_panel_bg != "0")
1807 end_pos.y += panel_bg_border * 2;
1809 if(panel_bg_padding)
1811 panel_pos += '1 1 0' * panel_bg_padding;
1812 panel_size -= '2 2 0' * panel_bg_padding;
1816 vector tmp = panel_size;
1818 float item_width = tmp.x / columns / rows;
1821 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1825 // column highlighting
1826 for (int i = 0; i < columns; ++i)
1828 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);
1831 for (int i = 0; i < rows; ++i)
1832 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1836 pos.x += item_width / 2;
1838 float oldposx = pos.x;
1842 IL_EACH(default_order_items, !is_item_filtered(it), {
1843 int n = g_inventory.inv_items[it.m_id];
1844 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1845 if (n <= 0) continue;
1846 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);
1848 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1849 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1850 tmpos.x += item_width * rows;
1851 pos.x += item_width * rows;
1852 if (rows == 2 && column == columns - 1) {
1860 panel_size.x += panel_bg_padding * 2; // restore initial width
1865 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1867 pos.x += hud_fontsize.x * 0.25;
1868 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1869 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1870 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1872 pos.y += hud_fontsize.y;
1877 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1878 float stat_secrets_found, stat_secrets_total;
1879 float stat_monsters_killed, stat_monsters_total;
1883 // get monster stats
1884 stat_monsters_killed = STAT(MONSTERS_KILLED);
1885 stat_monsters_total = STAT(MONSTERS_TOTAL);
1887 // get secrets stats
1888 stat_secrets_found = STAT(SECRETS_FOUND);
1889 stat_secrets_total = STAT(SECRETS_TOTAL);
1891 // get number of rows
1892 if(stat_secrets_total)
1894 if(stat_monsters_total)
1897 // if no rows, return
1901 // draw table header
1902 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1903 pos.y += 1.25 * hud_fontsize.y;
1904 if(panel.current_panel_bg != "0")
1905 pos.y += panel_bg_border;
1908 panel_size.y = hud_fontsize.y * rows;
1909 panel_size.y += panel_bg_padding * 2;
1912 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1913 if(panel.current_panel_bg != "0")
1914 end_pos.y += panel_bg_border * 2;
1916 if(panel_bg_padding)
1918 panel_pos += '1 1 0' * panel_bg_padding;
1919 panel_size -= '2 2 0' * panel_bg_padding;
1923 vector tmp = panel_size;
1926 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1929 if(stat_monsters_total)
1931 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1932 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1936 if(stat_secrets_total)
1938 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1939 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1942 panel_size.x += panel_bg_padding * 2; // restore initial width
1946 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1949 RANKINGS_RECEIVED_CNT = 0;
1950 for (i=RANKINGS_CNT-1; i>=0; --i)
1952 ++RANKINGS_RECEIVED_CNT;
1954 if (RANKINGS_RECEIVED_CNT == 0)
1957 vector hl_rgb = rgb + '0.5 0.5 0.5';
1959 vector scoreboard_selected_hl_pos = pos;
1961 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1962 pos.y += 1.25 * hud_fontsize.y;
1963 if(panel.current_panel_bg != "0")
1964 pos.y += panel_bg_border;
1966 vector scoreboard_selected_hl_size = '0 0 0';
1967 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1968 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1973 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1975 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1980 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1982 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1986 float ranksize = 3 * hud_fontsize.x;
1987 float timesize = 5 * hud_fontsize.x;
1988 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1989 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1990 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1993 rankings_cnt = RANKINGS_RECEIVED_CNT;
1994 rankings_rows = ceil(rankings_cnt / rankings_columns);
1997 // expand name column to fill the entire row
1998 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1999 namesize += available_space;
2000 columnsize.x += available_space;
2002 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2003 panel_size.y += panel_bg_padding * 2;
2004 scoreboard_selected_hl_size.y += panel_size.y;
2008 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2009 if(panel.current_panel_bg != "0")
2010 end_pos.y += panel_bg_border * 2;
2012 if(panel_bg_padding)
2014 panel_pos += '1 1 0' * panel_bg_padding;
2015 panel_size -= '2 2 0' * panel_bg_padding;
2021 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2023 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2025 int column = 0, j = 0;
2026 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2027 int start_item = rankings_start_column * rankings_rows;
2028 for(i = start_item; i < start_item + rankings_cnt; ++i)
2030 int t = grecordtime[i];
2034 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2035 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2036 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2037 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2039 str = count_ordinal(i+1);
2040 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2041 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2042 str = ColorTranslateRGB(grecordholder[i]);
2044 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2045 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2047 pos.y += 1.25 * hud_fontsize.y;
2049 if(j >= rankings_rows)
2053 pos.x += panel_size.x / rankings_columns;
2054 pos.y = panel_pos.y;
2057 strfree(zoned_name_self);
2059 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2061 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2062 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2065 panel_size.x += panel_bg_padding * 2; // restore initial width
2069 bool have_weapon_stats;
2070 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2072 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2074 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2077 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2078 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2084 if (!have_weapon_stats)
2086 FOREACH(Weapons, it != WEP_Null, {
2087 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2088 if (weapon_stats >= 0)
2090 have_weapon_stats = true;
2094 if (!have_weapon_stats)
2101 bool have_item_stats;
2102 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2104 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2106 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2109 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2110 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2116 if (!have_item_stats)
2118 IL_EACH(default_order_items, true, {
2119 if (!is_item_filtered(it))
2121 int q = g_inventory.inv_items[it.m_id];
2122 //q = 1; // debug: display all items
2125 have_item_stats = true;
2130 if (!have_item_stats)
2137 vector Scoreboard_Spectators_Draw(vector pos) {
2142 for(pl = players.sort_next; pl; pl = pl.sort_next)
2144 if(pl.team == NUM_SPECTATOR)
2146 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2147 if(tm.team == NUM_SPECTATOR)
2149 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2150 draw_beginBoldFont();
2151 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2153 pos.y += 1.25 * hud_fontsize.y;
2155 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2156 pos.y += 1.25 * hud_fontsize.y;
2161 if (str != "") // if there's at least one spectator
2162 pos.y += 0.5 * hud_fontsize.y;
2167 void Scoreboard_Draw()
2169 if(!autocvar__hud_configure)
2171 if(!hud_draw_maximized) return;
2173 // frametime checks allow to toggle the scoreboard even when the game is paused
2174 if(scoreboard_active) {
2175 if (scoreboard_fade_alpha == 0)
2176 scoreboard_time = time;
2177 if(hud_configure_menu_open == 1)
2178 scoreboard_fade_alpha = 1;
2179 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2180 if (scoreboard_fadeinspeed && frametime)
2181 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2183 scoreboard_fade_alpha = 1;
2184 if(hud_fontsize_str != autocvar_hud_fontsize)
2186 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2187 Scoreboard_initFieldSizes();
2188 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2192 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2193 if (scoreboard_fadeoutspeed && frametime)
2194 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2196 scoreboard_fade_alpha = 0;
2199 if (!scoreboard_fade_alpha)
2201 scoreboard_acc_fade_alpha = 0;
2202 scoreboard_itemstats_fade_alpha = 0;
2207 scoreboard_fade_alpha = 0;
2209 if (autocvar_hud_panel_scoreboard_dynamichud)
2212 HUD_Scale_Disable();
2214 if(scoreboard_fade_alpha <= 0)
2216 panel_fade_alpha *= scoreboard_fade_alpha;
2217 HUD_Panel_LoadCvars();
2219 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2220 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2221 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2222 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2223 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2224 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2225 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2227 // don't overlap with con_notify
2228 if(!autocvar__hud_configure)
2229 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2231 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2232 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2233 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2234 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2235 panel_pos.x = scoreboard_left;
2236 panel_size.x = fixed_scoreboard_width;
2238 Scoreboard_UpdatePlayerTeams();
2240 scoreboard_top = panel_pos.y;
2241 vector pos = panel_pos;
2246 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2248 // Begin of Game Info Section
2249 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2250 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2252 // Game Info: Game Type
2253 if (scoreboard_ui_enabled == 2)
2254 str = _("Team Selection");
2256 str = MapInfo_Type_ToText(gametype);
2257 draw_beginBoldFont();
2258 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);
2261 pos.y += sb_gameinfo_type_fontsize.y;
2262 // Game Info: Game Detail
2263 if (scoreboard_ui_enabled == 2)
2265 if (scoreboard_selected_team)
2266 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2268 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2269 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);
2271 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2272 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2273 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);
2277 float tl = STAT(TIMELIMIT);
2278 float fl = STAT(FRAGLIMIT);
2279 float ll = STAT(LEADLIMIT);
2280 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2283 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2284 if(!gametype.m_hidelimits)
2289 str = strcat(str, "^7 / "); // delimiter
2292 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2293 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2294 (teamscores_label(ts_primary) == "fastest") ? "" :
2295 TranslateScoresLabel(teamscores_label(ts_primary))));
2299 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2300 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2301 (scores_label(ps_primary) == "fastest") ? "" :
2302 TranslateScoresLabel(scores_label(ps_primary))));
2307 if(tl > 0 || fl > 0)
2310 if (ll_and_fl && fl > 0)
2311 str = strcat(str, "^7 & ");
2313 str = strcat(str, "^7 / ");
2318 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2319 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2320 (teamscores_label(ts_primary) == "fastest") ? "" :
2321 TranslateScoresLabel(teamscores_label(ts_primary))));
2325 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2326 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2327 (scores_label(ps_primary) == "fastest") ? "" :
2328 TranslateScoresLabel(scores_label(ps_primary))));
2332 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
2334 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2335 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2337 // End of Game Info Section
2339 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2340 if(panel.current_panel_bg != "0")
2341 pos.y += panel_bg_border;
2343 // Draw the scoreboard
2344 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2347 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2351 vector panel_bg_color_save = panel_bg_color;
2352 vector team_score_baseoffset;
2353 vector team_size_baseoffset;
2354 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2356 // put team score to the left of scoreboard (and team size to the right)
2357 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2358 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2359 if(panel.current_panel_bg != "0")
2361 team_score_baseoffset.x -= panel_bg_border;
2362 team_size_baseoffset.x += panel_bg_border;
2367 // put team score to the right of scoreboard (and team size to the left)
2368 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2369 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2370 if(panel.current_panel_bg != "0")
2372 team_score_baseoffset.x += panel_bg_border;
2373 team_size_baseoffset.x -= panel_bg_border;
2377 int team_size_total = 0;
2378 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2380 // calculate team size total (sum of all team sizes)
2381 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2382 if(tm.team != NUM_SPECTATOR)
2383 team_size_total += tm.team_size;
2386 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2388 if(tm.team == NUM_SPECTATOR)
2393 draw_beginBoldFont();
2394 vector rgb = Team_ColorRGB(tm.team);
2395 str = ftos(tm.(teamscores(ts_primary)));
2396 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2398 // team score on the left (default)
2399 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2403 // team score on the right
2404 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2406 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2408 // team size (if set to show on the side)
2409 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2411 // calculate the starting position for the whole team size info string
2412 str = sprintf("%d/%d", tm.team_size, team_size_total);
2413 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2415 // team size on the left
2416 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2420 // team size on the right
2421 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2423 str = sprintf("%d", tm.team_size);
2424 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2425 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2426 str = sprintf("/%d", team_size_total);
2427 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2431 // secondary score, e.g. keyhunt
2432 if(ts_primary != ts_secondary)
2434 str = ftos(tm.(teamscores(ts_secondary)));
2435 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2438 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2443 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2446 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2449 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2450 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2451 else if(panel_bg_color_team > 0)
2452 panel_bg_color = rgb * panel_bg_color_team;
2454 panel_bg_color = rgb;
2455 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2457 panel_bg_color = panel_bg_color_save;
2461 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2462 if(tm.team != NUM_SPECTATOR)
2465 // display it anyway
2466 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2469 // draw scoreboard spectators before accuracy and item stats
2470 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2471 pos = Scoreboard_Spectators_Draw(pos);
2474 // draw accuracy and item stats
2475 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2476 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2477 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2478 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2480 // draw scoreboard spectators after accuracy and item stats and before rankings
2481 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2482 pos = Scoreboard_Spectators_Draw(pos);
2485 if(MUTATOR_CALLHOOK(ShowRankings)) {
2486 string ranktitle = M_ARGV(0, string);
2487 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2488 if(race_speedaward_alltimebest)
2491 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2495 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2496 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2497 str = strcat(str, " / ");
2499 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2500 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2501 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2502 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2504 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2509 // draw scoreboard spectators after rankings
2510 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2511 pos = Scoreboard_Spectators_Draw(pos);
2514 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2516 // draw scoreboard spectators after mapstats
2517 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2518 pos = Scoreboard_Spectators_Draw(pos);
2522 // print information about respawn status
2523 float respawn_time = STAT(RESPAWN_TIME);
2524 if(!intermission && respawn_time)
2526 if(respawn_time < 0)
2528 // a negative number means we are awaiting respawn, time value is still the same
2529 respawn_time *= -1; // remove mark now that we checked it
2531 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2532 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2534 str = sprintf(_("^1Respawning in ^3%s^1..."),
2535 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2536 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2538 count_seconds(ceil(respawn_time - time))
2542 else if(time < respawn_time)
2544 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2545 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2546 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2548 count_seconds(ceil(respawn_time - time))
2552 else if(time >= respawn_time)
2553 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2555 pos.y += 1.2 * hud_fontsize.y;
2556 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2559 pos.y += hud_fontsize.y;
2560 if (scoreboard_fade_alpha < 1)
2561 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2562 else if (pos.y != scoreboard_bottom)
2564 if (pos.y > scoreboard_bottom)
2565 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2567 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2572 if (scoreboard_fade_alpha == 1)
2574 if (scoreboard_bottom > 0.95 * vid_conheight)
2575 rankings_rows = max(1, rankings_rows - 1);
2576 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2577 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2579 rankings_cnt = rankings_rows * rankings_columns;