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)
189 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
192 // release player's pressed keys as they aren't released elsewhere
193 // in particular jump needs to be released as it may open the team selection
194 // (when server detects jump has been pressed it sends the command to open the team selection)
195 Release_Common_Keys();
196 scoreboard_ui_enabled = 2;
197 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
201 if (scoreboard_ui_enabled == 1)
203 scoreboard_ui_enabled = 1;
204 scoreboard_selected_panel = SB_PANEL_FIRST;
206 scoreboard_selected_player = NULL;
207 scoreboard_selected_team = NULL;
208 scoreboard_selected_panel_time = time;
211 int rankings_start_column;
212 int rankings_rows = 0;
213 int rankings_columns = 0;
214 int rankings_cnt = 0;
215 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
219 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
224 mousepos.x = nPrimary;
225 mousepos.y = nSecondary;
232 // at this point bInputType can only be 0 or 1 (key pressed or released)
233 bool key_pressed = (bInputType == 0);
235 // ESC to exit (TAB-ESC works too)
236 if(nPrimary == K_ESCAPE)
240 HUD_Scoreboard_UI_Disable();
244 // block any input while a menu dialog is fading
245 if(autocvar__menu_alpha)
251 // allow console bind to work
252 string con_keys = findkeysforcommand("toggleconsole", 0);
253 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
255 bool hit_con_bind = false;
257 for (i = 0; i < keys; ++i)
259 if(nPrimary == stof(argv(i)))
264 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
265 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
266 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
267 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
270 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
271 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
272 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
273 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
276 if(nPrimary == K_TAB)
280 if (scoreboard_ui_enabled == 2)
282 if (hudShiftState & S_SHIFT)
285 goto downarrow_action;
288 if (hudShiftState & S_SHIFT)
290 --scoreboard_selected_panel;
291 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
292 --scoreboard_selected_panel;
293 if (scoreboard_selected_panel < SB_PANEL_FIRST)
294 scoreboard_selected_panel = SB_PANEL_MAX;
298 ++scoreboard_selected_panel;
299 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
300 ++scoreboard_selected_panel;
301 if (scoreboard_selected_panel > SB_PANEL_MAX)
302 scoreboard_selected_panel = SB_PANEL_FIRST;
305 scoreboard_selected_panel_time = time;
307 else if(nPrimary == K_DOWNARROW)
311 LABEL(downarrow_action);
312 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
314 if (scoreboard_ui_enabled == 2)
316 entity curr_team = NULL;
317 bool scoreboard_selected_team_found = false;
318 if (!scoreboard_selected_team)
319 scoreboard_selected_team_found = true;
321 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
323 if(tm.team == NUM_SPECTATOR)
326 if (scoreboard_selected_team_found)
328 if (scoreboard_selected_team == tm)
329 scoreboard_selected_team_found = true;
332 if (curr_team == scoreboard_selected_team) // loop reached the last team
334 scoreboard_selected_team = curr_team;
339 entity curr_pl = NULL;
340 bool scoreboard_selected_player_found = false;
341 if (!scoreboard_selected_player)
342 scoreboard_selected_player_found = true;
344 for(tm = teams.sort_next; tm; tm = tm.sort_next)
346 if(tm.team != NUM_SPECTATOR)
347 for(pl = players.sort_next; pl; pl = pl.sort_next)
349 if(pl.team != tm.team)
352 if (scoreboard_selected_player_found)
354 if (scoreboard_selected_player == pl)
355 scoreboard_selected_player_found = true;
359 if (curr_pl == scoreboard_selected_player) // loop reached the last player
361 scoreboard_selected_player = curr_pl;
365 else if(nPrimary == K_UPARROW)
369 LABEL(uparrow_action);
370 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
372 if (scoreboard_ui_enabled == 2)
374 entity prev_team = NULL;
375 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
377 if(tm.team == NUM_SPECTATOR)
379 if (tm == scoreboard_selected_team)
384 scoreboard_selected_team = prev_team;
388 entity prev_pl = NULL;
390 for(tm = teams.sort_next; tm; tm = tm.sort_next)
392 if(tm.team != NUM_SPECTATOR)
393 for(pl = players.sort_next; pl; pl = pl.sort_next)
395 if(pl.team != tm.team)
397 if (pl == scoreboard_selected_player)
403 scoreboard_selected_player = prev_pl;
407 else if(nPrimary == K_RIGHTARROW)
411 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
412 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
414 else if(nPrimary == K_LEFTARROW)
418 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
419 rankings_start_column = max(rankings_start_column - 1, 0);
421 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
425 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
427 if (scoreboard_ui_enabled == 2)
430 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
433 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
434 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
435 HUD_Scoreboard_UI_Disable();
437 else if (scoreboard_selected_player)
438 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
441 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
445 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
447 switch (scoreboard_selected_columns_layout)
450 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
452 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
453 scoreboard_selected_columns_layout = 1;
458 localcmd(sprintf("scoreboard_columns_set default\n"));
459 scoreboard_selected_columns_layout = 2;
462 localcmd(sprintf("scoreboard_columns_set all\n"));
463 scoreboard_selected_columns_layout = 0;
468 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
472 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
474 if (scoreboard_selected_player)
476 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
477 HUD_Scoreboard_UI_Disable();
481 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
485 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
487 if (scoreboard_selected_player)
488 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
491 else if(hit_con_bind || nPrimary == K_PAUSE)
497 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
498 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
500 #define SB_EXTRA_SORTING_FIELDS 5
501 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
502 void Scoreboard_InitScores()
506 ps_primary = ps_secondary = NULL;
507 ts_primary = ts_secondary = -1;
508 FOREACH(Scores, true, {
509 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
510 if(f == SFL_SORT_PRIO_PRIMARY)
512 if(f == SFL_SORT_PRIO_SECONDARY)
514 if(ps_primary == it || ps_secondary == it)
516 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
517 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
518 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
519 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
520 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
522 if(ps_secondary == NULL)
523 ps_secondary = ps_primary;
525 for(i = 0; i < MAX_TEAMSCORE; ++i)
527 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
528 if(f == SFL_SORT_PRIO_PRIMARY)
530 if(f == SFL_SORT_PRIO_SECONDARY)
533 if(ts_secondary == -1)
534 ts_secondary = ts_primary;
536 Cmd_Scoreboard_SetFields(0);
540 void Scoreboard_UpdatePlayerTeams()
542 static float update_time;
543 if (time <= update_time)
549 for(pl = players.sort_next; pl; pl = pl.sort_next)
552 int Team = entcs_GetScoreTeam(pl.sv_entnum);
553 if(SetTeam(pl, Team))
556 Scoreboard_UpdatePlayerPos(pl);
560 pl = players.sort_next;
565 print(strcat("PNUM: ", ftos(num), "\n"));
570 int Scoreboard_CompareScore(int vl, int vr, int f)
572 TC(int, vl); TC(int, vr); TC(int, f);
573 if(f & SFL_ZERO_IS_WORST)
575 if(vl == 0 && vr != 0)
577 if(vl != 0 && vr == 0)
581 return IS_INCREASING(f);
583 return IS_DECREASING(f);
587 float Scoreboard_ComparePlayerScores(entity left, entity right)
589 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
590 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
597 if(vl == NUM_SPECTATOR)
599 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
601 if(!left.gotscores && right.gotscores)
608 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
612 if (!fld) fld = ps_primary;
613 else if (ps_secondary == ps_primary) continue;
614 else fld = ps_secondary;
618 fld = sb_extra_sorting_field[i];
619 if (fld == ps_primary || fld == ps_secondary) continue;
623 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
624 if (r >= 0) return r;
627 if (left.sv_entnum < right.sv_entnum)
633 void Scoreboard_UpdatePlayerPos(entity player)
636 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
638 SORT_SWAP(player, ent);
640 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
642 SORT_SWAP(ent, player);
646 float Scoreboard_CompareTeamScores(entity left, entity right)
648 if(left.team == NUM_SPECTATOR)
650 if(right.team == NUM_SPECTATOR)
655 for(int i = -2; i < MAX_TEAMSCORE; ++i)
659 if (fld_idx == -1) fld_idx = ts_primary;
660 else if (ts_secondary == ts_primary) continue;
661 else fld_idx = ts_secondary;
666 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
669 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
670 if (r >= 0) return r;
673 if (left.team < right.team)
679 void Scoreboard_UpdateTeamPos(entity Team)
682 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
684 SORT_SWAP(Team, ent);
686 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
688 SORT_SWAP(ent, Team);
692 void Cmd_Scoreboard_Help()
694 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
695 LOG_HELP(_("Usage:"));
696 LOG_HELP("^2scoreboard_columns_set ^3default");
697 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
698 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
699 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
700 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
701 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
702 LOG_HELP(_("The following field names are recognized (case insensitive):"));
708 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
709 "of game types, then a slash, to make the field show up only in these\n"
710 "or in all but these game types. You can also specify 'all' as a\n"
711 "field to show all fields available for the current game mode."));
714 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
715 "include/exclude ALL teams/noteams game modes."));
718 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
719 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
720 "right of the vertical bar aligned to the right."));
721 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
722 "other gamemodes except DM."));
725 // NOTE: adding a gametype with ? to not warn for an optional field
726 // make sure it's excluded in a previous exclusive rule, if any
727 // otherwise the previous exclusive rule warns anyway
728 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
729 #define SCOREBOARD_DEFAULT_COLUMNS \
730 "ping pl fps name |" \
731 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
732 " -teams,lms/deaths +ft,tdm/deaths" \
734 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
735 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
736 " +tdm,ft,dom,ons,as/teamkills"\
737 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
738 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
739 " +lms/lives +lms/rank" \
740 " +kh/kckills +kh/losses +kh/caps" \
741 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
742 " +as/objectives +nb/faults +nb/goals" \
743 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
744 " +dom/ticks +dom/takes" \
745 " -lms,rc,cts,inv,nb/score"
747 void Cmd_Scoreboard_SetFields(int argc)
752 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
756 return; // do nothing, we don't know gametype and scores yet
758 // sbt_fields uses strunzone on the titles!
759 if(!sbt_field_title[0])
760 for(i = 0; i < MAX_SBT_FIELDS; ++i)
761 sbt_field_title[i] = strzone("(null)");
763 // TODO: re enable with gametype dependant cvars?
764 if(argc < 3) // no arguments provided
765 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
768 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
772 if(argv(2) == "default" || argv(2) == "expand_default")
774 if(argv(2) == "expand_default")
775 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
776 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
778 else if(argv(2) == "all" || argv(2) == "ALL")
780 string s = "ping pl name |"; // scores without label (not really scores)
783 // scores without label
784 s = strcat(s, " ", "sum");
785 s = strcat(s, " ", "kdratio");
786 s = strcat(s, " ", "frags");
788 FOREACH(Scores, true, {
790 if(it != ps_secondary)
791 if(scores_label(it) != "")
792 s = strcat(s, " ", scores_label(it));
794 if(ps_secondary != ps_primary)
795 s = strcat(s, " ", scores_label(ps_secondary));
796 s = strcat(s, " ", scores_label(ps_primary));
797 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
804 hud_fontsize = HUD_GetFontsize("hud_fontsize");
806 for(i = 1; i < argc - 1; ++i)
809 bool nocomplain = false;
810 if(substring(str, 0, 1) == "?")
813 str = substring(str, 1, strlen(str) - 1);
816 slash = strstrofs(str, "/", 0);
819 pattern = substring(str, 0, slash);
820 str = substring(str, slash + 1, strlen(str) - (slash + 1));
822 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
826 str = strtolower(str);
827 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
828 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
833 // fields without a label (not networked via the score system)
834 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
835 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
836 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
837 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
838 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
839 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
840 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
841 default: // fields with a label
843 // map alternative labels
844 if (str == "damage") str = "dmg";
845 if (str == "damagetaken") str = "dmgtaken";
847 FOREACH(Scores, true, {
848 if (str == strtolower(scores_label(it))) {
850 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
854 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
855 if(!nocomplain && str != "fps") // server can disable the fps field
856 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
858 strfree(sbt_field_title[sbt_num_fields]);
859 sbt_field_size[sbt_num_fields] = 0;
863 sbt_field[sbt_num_fields] = j;
866 if(j == ps_secondary)
867 have_secondary = true;
872 if(sbt_num_fields >= MAX_SBT_FIELDS)
876 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
878 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
879 have_secondary = true;
880 if(ps_primary == ps_secondary)
881 have_secondary = true;
882 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
884 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
888 strfree(sbt_field_title[sbt_num_fields]);
889 for(i = sbt_num_fields; i > 0; --i)
891 sbt_field_title[i] = sbt_field_title[i-1];
892 sbt_field_size[i] = sbt_field_size[i-1];
893 sbt_field[i] = sbt_field[i-1];
895 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
896 sbt_field[0] = SP_NAME;
898 LOG_INFO("fixed missing field 'name'");
902 strfree(sbt_field_title[sbt_num_fields]);
903 for(i = sbt_num_fields; i > 1; --i)
905 sbt_field_title[i] = sbt_field_title[i-1];
906 sbt_field_size[i] = sbt_field_size[i-1];
907 sbt_field[i] = sbt_field[i-1];
909 sbt_field_title[1] = strzone("|");
910 sbt_field[1] = SP_SEPARATOR;
911 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
913 LOG_INFO("fixed missing field '|'");
916 else if(!have_separator)
918 strcpy(sbt_field_title[sbt_num_fields], "|");
919 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
920 sbt_field[sbt_num_fields] = SP_SEPARATOR;
922 LOG_INFO("fixed missing field '|'");
926 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
927 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
928 sbt_field[sbt_num_fields] = ps_secondary;
930 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
934 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
935 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
936 sbt_field[sbt_num_fields] = ps_primary;
938 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
942 sbt_field[sbt_num_fields] = SP_END;
945 string Scoreboard_AddPlayerId(string pl_name, entity pl)
947 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
948 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
949 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
953 vector sbt_field_rgb;
954 string sbt_field_icon0;
955 string sbt_field_icon1;
956 string sbt_field_icon2;
957 vector sbt_field_icon0_rgb;
958 vector sbt_field_icon1_rgb;
959 vector sbt_field_icon2_rgb;
960 string Scoreboard_GetName(entity pl)
962 if(ready_waiting && pl.ready)
964 sbt_field_icon0 = "gfx/scoreboard/player_ready";
968 int f = entcs_GetClientColors(pl.sv_entnum);
970 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
971 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
972 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
973 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
974 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
977 return entcs_GetName(pl.sv_entnum);
980 string Scoreboard_GetField(entity pl, PlayerScoreField field)
982 float tmp, num, denom;
985 sbt_field_rgb = '1 1 1';
986 sbt_field_icon0 = "";
987 sbt_field_icon1 = "";
988 sbt_field_icon2 = "";
989 sbt_field_icon0_rgb = '1 1 1';
990 sbt_field_icon1_rgb = '1 1 1';
991 sbt_field_icon2_rgb = '1 1 1';
996 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
997 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1001 tmp = max(0, min(220, f-80)) / 220;
1002 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1008 f = pl.ping_packetloss;
1009 tmp = pl.ping_movementloss;
1010 if(f == 0 && tmp == 0)
1012 str = ftos(ceil(f * 100));
1014 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1015 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1016 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1020 str = Scoreboard_GetName(pl);
1021 if (autocvar_hud_panel_scoreboard_playerid)
1022 str = Scoreboard_AddPlayerId(str, pl);
1026 f = pl.(scores(SP_KILLS));
1027 f -= pl.(scores(SP_SUICIDES));
1031 num = pl.(scores(SP_KILLS));
1032 denom = pl.(scores(SP_DEATHS));
1035 sbt_field_rgb = '0 1 0';
1036 str = sprintf("%d", num);
1037 } else if(num <= 0) {
1038 sbt_field_rgb = '1 0 0';
1039 str = sprintf("%.1f", num/denom);
1041 str = sprintf("%.1f", num/denom);
1045 f = pl.(scores(SP_KILLS));
1046 f -= pl.(scores(SP_DEATHS));
1049 sbt_field_rgb = '0 1 0';
1051 sbt_field_rgb = '1 1 1';
1053 sbt_field_rgb = '1 0 0';
1059 float elo = pl.(scores(SP_ELO));
1061 case -1: return "...";
1062 case -2: return _("N/A");
1063 default: return ftos(elo);
1069 float fps = pl.(scores(SP_FPS));
1072 sbt_field_rgb = '1 1 1';
1073 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1075 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1076 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1080 case SP_DMG: case SP_DMGTAKEN:
1081 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1083 default: case SP_SCORE:
1084 tmp = pl.(scores(field));
1085 f = scores_flags(field);
1086 if(field == ps_primary)
1087 sbt_field_rgb = '1 1 0';
1088 else if(field == ps_secondary)
1089 sbt_field_rgb = '0 1 1';
1091 sbt_field_rgb = '1 1 1';
1092 return ScoreString(f, tmp);
1097 float sbt_fixcolumnwidth_len;
1098 float sbt_fixcolumnwidth_iconlen;
1099 float sbt_fixcolumnwidth_marginlen;
1101 string Scoreboard_FixColumnWidth(int i, string str)
1107 sbt_fixcolumnwidth_iconlen = 0;
1109 if(sbt_field_icon0 != "")
1111 sz = draw_getimagesize(sbt_field_icon0);
1113 if(sbt_fixcolumnwidth_iconlen < f)
1114 sbt_fixcolumnwidth_iconlen = f;
1117 if(sbt_field_icon1 != "")
1119 sz = draw_getimagesize(sbt_field_icon1);
1121 if(sbt_fixcolumnwidth_iconlen < f)
1122 sbt_fixcolumnwidth_iconlen = f;
1125 if(sbt_field_icon2 != "")
1127 sz = draw_getimagesize(sbt_field_icon2);
1129 if(sbt_fixcolumnwidth_iconlen < f)
1130 sbt_fixcolumnwidth_iconlen = f;
1133 if(sbt_fixcolumnwidth_iconlen != 0)
1135 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1136 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1139 sbt_fixcolumnwidth_marginlen = 0;
1141 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1144 float remaining_space = 0;
1145 for(j = 0; j < sbt_num_fields; ++j)
1147 if (sbt_field[i] != SP_SEPARATOR)
1148 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1149 sbt_field_size[i] = panel_size.x - remaining_space;
1151 if (sbt_fixcolumnwidth_iconlen != 0)
1152 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1153 float namesize = panel_size.x - remaining_space;
1154 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1155 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1157 max_namesize = vid_conwidth - remaining_space;
1160 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1162 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1163 if(sbt_field_size[i] < f)
1164 sbt_field_size[i] = f;
1169 void Scoreboard_initFieldSizes()
1171 for(int i = 0; i < sbt_num_fields; ++i)
1173 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1174 Scoreboard_FixColumnWidth(i, "");
1178 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1181 vector column_dim = eY * panel_size.y;
1183 column_dim.y -= 1.25 * hud_fontsize.y;
1184 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1185 pos.x += hud_fontsize.x * 0.5;
1186 for(i = 0; i < sbt_num_fields; ++i)
1188 if(sbt_field[i] == SP_SEPARATOR)
1190 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1193 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1194 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1195 pos.x += column_dim.x;
1197 if(sbt_field[i] == SP_SEPARATOR)
1199 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1200 for(i = sbt_num_fields - 1; i > 0; --i)
1202 if(sbt_field[i] == SP_SEPARATOR)
1205 pos.x -= sbt_field_size[i];
1210 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1211 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1214 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1215 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1216 pos.x -= hud_fontsize.x;
1220 pos.x = panel_pos.x;
1221 pos.y += 1.25 * hud_fontsize.y;
1225 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1227 TC(bool, is_self); TC(int, pl_number);
1229 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1231 vector h_pos = item_pos;
1232 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1233 // alternated rows highlighting
1234 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1236 if (pl == scoreboard_selected_player)
1237 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1240 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1241 else if((sbt_highlight) && (!(pl_number % 2)))
1242 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1244 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1246 vector pos = item_pos;
1247 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1249 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1251 pos.x += hud_fontsize.x * 0.5;
1252 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1253 vector tmp = '0 0 0';
1255 PlayerScoreField field;
1256 for(i = 0; i < sbt_num_fields; ++i)
1258 field = sbt_field[i];
1259 if(field == SP_SEPARATOR)
1262 if(is_spec && field != SP_NAME && field != SP_PING) {
1263 pos.x += sbt_field_size[i] + hud_fontsize.x;
1266 str = Scoreboard_GetField(pl, field);
1267 str = Scoreboard_FixColumnWidth(i, str);
1269 pos.x += sbt_field_size[i] + hud_fontsize.x;
1271 if(field == SP_NAME) {
1272 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1273 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1275 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1276 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1279 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1280 if(sbt_field_icon0 != "")
1281 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1282 if(sbt_field_icon1 != "")
1283 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1284 if(sbt_field_icon2 != "")
1285 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1288 if(sbt_field[i] == SP_SEPARATOR)
1290 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1291 for(i = sbt_num_fields-1; i > 0; --i)
1293 field = sbt_field[i];
1294 if(field == SP_SEPARATOR)
1297 if(is_spec && field != SP_NAME && field != SP_PING) {
1298 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1302 str = Scoreboard_GetField(pl, field);
1303 str = Scoreboard_FixColumnWidth(i, str);
1305 if(field == SP_NAME) {
1306 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1307 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1309 tmp.x = sbt_fixcolumnwidth_len;
1310 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1313 tmp.x = sbt_field_size[i];
1314 if(sbt_field_icon0 != "")
1315 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1316 if(sbt_field_icon1 != "")
1317 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1318 if(sbt_field_icon2 != "")
1319 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1320 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1325 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1328 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1331 vector h_pos = item_pos;
1332 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1334 bool complete = (this_team == NUM_SPECTATOR);
1337 if((sbt_highlight) && (!(pl_number % 2)))
1338 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1340 vector pos = item_pos;
1341 pos.x += hud_fontsize.x * 0.5;
1342 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1344 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1346 width_limit -= stringwidth("...", false, hud_fontsize);
1347 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1348 static float max_name_width = 0;
1350 float fieldsize = 0;
1351 float min_fieldsize = 0;
1352 float fieldpadding = hud_fontsize.x * 0.25;
1353 if(this_team == NUM_SPECTATOR)
1355 if(autocvar_hud_panel_scoreboard_spectators_showping)
1356 min_fieldsize = stringwidth("999", false, hud_fontsize);
1358 else if(autocvar_hud_panel_scoreboard_others_showscore)
1359 min_fieldsize = stringwidth("99", false, hud_fontsize);
1360 for(i = 0; pl; pl = pl.sort_next)
1362 if(pl.team != this_team)
1364 if(pl == ignored_pl)
1368 if(this_team == NUM_SPECTATOR)
1370 if(autocvar_hud_panel_scoreboard_spectators_showping)
1371 field = Scoreboard_GetField(pl, SP_PING);
1373 else if(autocvar_hud_panel_scoreboard_others_showscore)
1374 field = Scoreboard_GetField(pl, SP_SCORE);
1376 string str = entcs_GetName(pl.sv_entnum);
1377 if (autocvar_hud_panel_scoreboard_playerid)
1378 str = Scoreboard_AddPlayerId(str, pl);
1379 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1380 float column_width = stringwidth(str, true, hud_fontsize);
1381 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1383 if(column_width > max_name_width)
1384 max_name_width = column_width;
1385 column_width = max_name_width;
1389 fieldsize = stringwidth(field, false, hud_fontsize);
1390 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1393 if(pos.x + column_width > width_limit)
1398 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1403 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1404 pos.y += hud_fontsize.y * 1.25;
1408 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1410 if (pl == scoreboard_selected_player)
1412 h_size.x = column_width + hud_fontsize.x * 0.25;
1413 h_size.y = hud_fontsize.y;
1414 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1418 vector name_pos = pos;
1419 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1420 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1421 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1424 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1425 h_size.y = hud_fontsize.y;
1426 vector field_pos = pos;
1427 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1428 field_pos.x += column_width - h_size.x;
1430 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1431 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1432 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1436 h_size.x = column_width + hud_fontsize.x * 0.25;
1437 h_size.y = hud_fontsize.y;
1438 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1440 pos.x += column_width;
1441 pos.x += hud_fontsize.x;
1443 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1446 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1448 int max_players = 999;
1449 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1451 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1454 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1455 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1456 height /= team_count;
1459 height -= panel_bg_padding * 2; // - padding
1460 max_players = floor(height / (hud_fontsize.y * 1.25));
1461 if(max_players <= 1)
1463 if(max_players == tm.team_size)
1468 entity me = playerslots[current_player];
1470 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1471 panel_size.y += panel_bg_padding * 2;
1473 vector scoreboard_selected_hl_pos = pos;
1474 vector scoreboard_selected_hl_size = '0 0 0';
1475 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1476 scoreboard_selected_hl_size.y = panel_size.y;
1480 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1481 if(panel.current_panel_bg != "0")
1482 end_pos.y += panel_bg_border * 2;
1484 if(panel_bg_padding)
1486 panel_pos += '1 1 0' * panel_bg_padding;
1487 panel_size -= '2 2 0' * panel_bg_padding;
1491 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1495 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1497 pos.y += 1.25 * hud_fontsize.y;
1500 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1502 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1505 // print header row and highlight columns
1506 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1508 // fill the table and draw the rows
1509 bool is_self = false;
1510 bool self_shown = false;
1512 for(pl = players.sort_next; pl; pl = pl.sort_next)
1514 if(pl.team != tm.team)
1516 if(i == max_players - 2 && pl != me)
1518 if(!self_shown && me.team == tm.team)
1520 Scoreboard_DrawItem(pos, rgb, me, true, i);
1522 pos.y += 1.25 * hud_fontsize.y;
1526 if(i >= max_players - 1)
1528 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1531 is_self = (pl.sv_entnum == current_player);
1532 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1535 pos.y += 1.25 * hud_fontsize.y;
1539 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1541 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1543 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1544 _alpha *= panel_fg_alpha;
1546 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1550 panel_size.x += panel_bg_padding * 2; // restore initial width
1554 bool Scoreboard_WouldDraw()
1556 if (scoreboard_ui_enabled)
1558 if (scoreboard_ui_disabling)
1560 if (scoreboard_fade_alpha == 0)
1561 HUD_Scoreboard_UI_Disable_Instantly();
1564 if (intermission && scoreboard_ui_enabled == 2)
1566 HUD_Scoreboard_UI_Disable_Instantly();
1571 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1573 else if (QuickMenu_IsOpened())
1575 else if (HUD_Radar_Clickable())
1577 else if (scoreboard_showscores)
1579 else if (intermission == 1)
1581 else if (intermission == 2)
1583 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1584 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1588 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1593 float average_accuracy;
1594 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1596 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1598 WepSet weapons_stat = WepSet_GetFromStat();
1599 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1600 int disownedcnt = 0;
1602 FOREACH(Weapons, it != WEP_Null, {
1603 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1605 WepSet set = it.m_wepset;
1606 if(it.spawnflags & WEP_TYPE_OTHER)
1611 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1613 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1620 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1621 if (weapon_cnt <= 0) return pos;
1624 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1626 int columns = ceil(weapon_cnt / rows);
1628 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1629 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1630 float height = weapon_height + hud_fontsize.y;
1632 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);
1633 pos.y += 1.25 * hud_fontsize.y;
1634 if(panel.current_panel_bg != "0")
1635 pos.y += panel_bg_border;
1638 panel_size.y = height * rows;
1639 panel_size.y += panel_bg_padding * 2;
1641 float panel_bg_alpha_save = panel_bg_alpha;
1642 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1644 panel_bg_alpha = panel_bg_alpha_save;
1646 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1647 if(panel.current_panel_bg != "0")
1648 end_pos.y += panel_bg_border * 2;
1650 if(panel_bg_padding)
1652 panel_pos += '1 1 0' * panel_bg_padding;
1653 panel_size -= '2 2 0' * panel_bg_padding;
1657 vector tmp = panel_size;
1659 float weapon_width = tmp.x / columns / rows;
1662 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1666 // column highlighting
1667 for (int i = 0; i < columns; ++i)
1669 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);
1672 for (int i = 0; i < rows; ++i)
1673 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1676 average_accuracy = 0;
1677 int weapons_with_stats = 0;
1679 pos.x += weapon_width / 2;
1681 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1684 Accuracy_LoadColors();
1686 float oldposx = pos.x;
1690 FOREACH(Weapons, it != WEP_Null, {
1691 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1693 WepSet set = it.m_wepset;
1694 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1696 if (it.spawnflags & WEP_TYPE_OTHER)
1700 if (weapon_stats >= 0)
1701 weapon_alpha = sbt_fg_alpha;
1703 weapon_alpha = 0.2 * sbt_fg_alpha;
1706 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1708 if (weapon_stats >= 0) {
1709 weapons_with_stats += 1;
1710 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1712 string s = sprintf("%d%%", weapon_stats * 100);
1713 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1715 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1716 rgb = Accuracy_GetColor(weapon_stats);
1718 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1720 tmpos.x += weapon_width * rows;
1721 pos.x += weapon_width * rows;
1722 if (rows == 2 && column == columns - 1) {
1730 if (weapons_with_stats)
1731 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1733 panel_size.x += panel_bg_padding * 2; // restore initial width
1738 bool is_item_filtered(entity it)
1740 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1742 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1745 if (it.instanceOfArmor || it.instanceOfHealth)
1747 int ha_mask = floor(mask) % 10;
1750 default: return false;
1751 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1752 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1753 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1754 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1757 if (it.instanceOfAmmo)
1759 int ammo_mask = floor(mask / 10) % 10;
1760 return (ammo_mask == 1);
1765 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1767 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1769 int disowned_cnt = 0;
1770 int uninteresting_cnt = 0;
1771 IL_EACH(default_order_items, true, {
1772 int q = g_inventory.inv_items[it.m_id];
1773 //q = 1; // debug: display all items
1774 if (is_item_filtered(it))
1775 ++uninteresting_cnt;
1779 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1780 int n = items_cnt - disowned_cnt;
1781 if (n <= 0) return pos;
1783 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1784 int columns = max(6, ceil(n / rows));
1786 float item_height = hud_fontsize.y * 2.3;
1787 float height = item_height + hud_fontsize.y;
1789 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1790 pos.y += 1.25 * hud_fontsize.y;
1791 if(panel.current_panel_bg != "0")
1792 pos.y += panel_bg_border;
1795 panel_size.y = height * rows;
1796 panel_size.y += panel_bg_padding * 2;
1798 float panel_bg_alpha_save = panel_bg_alpha;
1799 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1801 panel_bg_alpha = panel_bg_alpha_save;
1803 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1804 if(panel.current_panel_bg != "0")
1805 end_pos.y += panel_bg_border * 2;
1807 if(panel_bg_padding)
1809 panel_pos += '1 1 0' * panel_bg_padding;
1810 panel_size -= '2 2 0' * panel_bg_padding;
1814 vector tmp = panel_size;
1816 float item_width = tmp.x / columns / rows;
1819 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1823 // column highlighting
1824 for (int i = 0; i < columns; ++i)
1826 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);
1829 for (int i = 0; i < rows; ++i)
1830 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1834 pos.x += item_width / 2;
1836 float oldposx = pos.x;
1840 IL_EACH(default_order_items, !is_item_filtered(it), {
1841 int n = g_inventory.inv_items[it.m_id];
1842 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1843 if (n <= 0) continue;
1844 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);
1846 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1847 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1848 tmpos.x += item_width * rows;
1849 pos.x += item_width * rows;
1850 if (rows == 2 && column == columns - 1) {
1858 panel_size.x += panel_bg_padding * 2; // restore initial width
1863 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1865 pos.x += hud_fontsize.x * 0.25;
1866 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1867 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1868 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1870 pos.y += hud_fontsize.y;
1875 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1876 float stat_secrets_found, stat_secrets_total;
1877 float stat_monsters_killed, stat_monsters_total;
1881 // get monster stats
1882 stat_monsters_killed = STAT(MONSTERS_KILLED);
1883 stat_monsters_total = STAT(MONSTERS_TOTAL);
1885 // get secrets stats
1886 stat_secrets_found = STAT(SECRETS_FOUND);
1887 stat_secrets_total = STAT(SECRETS_TOTAL);
1889 // get number of rows
1890 if(stat_secrets_total)
1892 if(stat_monsters_total)
1895 // if no rows, return
1899 // draw table header
1900 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1901 pos.y += 1.25 * hud_fontsize.y;
1902 if(panel.current_panel_bg != "0")
1903 pos.y += panel_bg_border;
1906 panel_size.y = hud_fontsize.y * rows;
1907 panel_size.y += panel_bg_padding * 2;
1910 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1911 if(panel.current_panel_bg != "0")
1912 end_pos.y += panel_bg_border * 2;
1914 if(panel_bg_padding)
1916 panel_pos += '1 1 0' * panel_bg_padding;
1917 panel_size -= '2 2 0' * panel_bg_padding;
1921 vector tmp = panel_size;
1924 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1927 if(stat_monsters_total)
1929 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1930 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1934 if(stat_secrets_total)
1936 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1937 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1940 panel_size.x += panel_bg_padding * 2; // restore initial width
1944 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1947 RANKINGS_RECEIVED_CNT = 0;
1948 for (i=RANKINGS_CNT-1; i>=0; --i)
1950 ++RANKINGS_RECEIVED_CNT;
1952 if (RANKINGS_RECEIVED_CNT == 0)
1955 vector hl_rgb = rgb + '0.5 0.5 0.5';
1957 vector scoreboard_selected_hl_pos = pos;
1959 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1960 pos.y += 1.25 * hud_fontsize.y;
1961 if(panel.current_panel_bg != "0")
1962 pos.y += panel_bg_border;
1964 vector scoreboard_selected_hl_size = '0 0 0';
1965 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1966 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1971 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1973 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1978 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1980 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1984 float ranksize = 3 * hud_fontsize.x;
1985 float timesize = 5 * hud_fontsize.x;
1986 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1987 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1988 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1991 rankings_cnt = RANKINGS_RECEIVED_CNT;
1992 rankings_rows = ceil(rankings_cnt / rankings_columns);
1995 // expand name column to fill the entire row
1996 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1997 namesize += available_space;
1998 columnsize.x += available_space;
2000 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2001 panel_size.y += panel_bg_padding * 2;
2002 scoreboard_selected_hl_size.y += panel_size.y;
2006 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2007 if(panel.current_panel_bg != "0")
2008 end_pos.y += panel_bg_border * 2;
2010 if(panel_bg_padding)
2012 panel_pos += '1 1 0' * panel_bg_padding;
2013 panel_size -= '2 2 0' * panel_bg_padding;
2019 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2021 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2023 int column = 0, j = 0;
2024 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2025 int start_item = rankings_start_column * rankings_rows;
2026 for(i = start_item; i < start_item + rankings_cnt; ++i)
2028 int t = grecordtime[i];
2032 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2033 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2034 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2035 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2037 str = count_ordinal(i+1);
2038 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2039 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2040 str = ColorTranslateRGB(grecordholder[i]);
2042 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2043 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2045 pos.y += 1.25 * hud_fontsize.y;
2047 if(j >= rankings_rows)
2051 pos.x += panel_size.x / rankings_columns;
2052 pos.y = panel_pos.y;
2055 strfree(zoned_name_self);
2057 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2059 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2060 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2063 panel_size.x += panel_bg_padding * 2; // restore initial width
2067 bool have_weapon_stats;
2068 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2070 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2072 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2075 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2076 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2082 if (!have_weapon_stats)
2084 FOREACH(Weapons, it != WEP_Null, {
2085 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2086 if (weapon_stats >= 0)
2088 have_weapon_stats = true;
2092 if (!have_weapon_stats)
2099 bool have_item_stats;
2100 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2102 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2104 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2107 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2108 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2114 if (!have_item_stats)
2116 IL_EACH(default_order_items, true, {
2117 if (!is_item_filtered(it))
2119 int q = g_inventory.inv_items[it.m_id];
2120 //q = 1; // debug: display all items
2123 have_item_stats = true;
2128 if (!have_item_stats)
2135 vector Scoreboard_Spectators_Draw(vector pos) {
2140 for(pl = players.sort_next; pl; pl = pl.sort_next)
2142 if(pl.team == NUM_SPECTATOR)
2144 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2145 if(tm.team == NUM_SPECTATOR)
2147 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2148 draw_beginBoldFont();
2149 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2151 pos.y += 1.25 * hud_fontsize.y;
2153 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2154 pos.y += 1.25 * hud_fontsize.y;
2159 if (str != "") // if there's at least one spectator
2160 pos.y += 0.5 * hud_fontsize.y;
2165 void Scoreboard_Draw()
2167 if(!autocvar__hud_configure)
2169 if(!hud_draw_maximized) return;
2171 // frametime checks allow to toggle the scoreboard even when the game is paused
2172 if(scoreboard_active) {
2173 if (scoreboard_fade_alpha == 0)
2174 scoreboard_time = time;
2175 if(hud_configure_menu_open == 1)
2176 scoreboard_fade_alpha = 1;
2177 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2178 if (scoreboard_fadeinspeed && frametime)
2179 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2181 scoreboard_fade_alpha = 1;
2182 if(hud_fontsize_str != autocvar_hud_fontsize)
2184 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2185 Scoreboard_initFieldSizes();
2186 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2190 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2191 if (scoreboard_fadeoutspeed && frametime)
2192 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2194 scoreboard_fade_alpha = 0;
2197 if (!scoreboard_fade_alpha)
2199 scoreboard_acc_fade_alpha = 0;
2200 scoreboard_itemstats_fade_alpha = 0;
2205 scoreboard_fade_alpha = 0;
2207 if (autocvar_hud_panel_scoreboard_dynamichud)
2210 HUD_Scale_Disable();
2212 if(scoreboard_fade_alpha <= 0)
2214 panel_fade_alpha *= scoreboard_fade_alpha;
2215 HUD_Panel_LoadCvars();
2217 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2218 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2219 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2220 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2221 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2222 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2223 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2225 // don't overlap with con_notify
2226 if(!autocvar__hud_configure)
2227 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2229 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2230 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2231 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2232 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2233 panel_pos.x = scoreboard_left;
2234 panel_size.x = fixed_scoreboard_width;
2236 Scoreboard_UpdatePlayerTeams();
2238 scoreboard_top = panel_pos.y;
2239 vector pos = panel_pos;
2244 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2246 // Begin of Game Info Section
2247 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2248 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2250 // Game Info: Game Type
2251 if (scoreboard_ui_enabled == 2)
2252 str = _("Team Selection");
2254 str = MapInfo_Type_ToText(gametype);
2255 draw_beginBoldFont();
2256 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);
2259 pos.y += sb_gameinfo_type_fontsize.y;
2260 // Game Info: Game Detail
2261 if (scoreboard_ui_enabled == 2)
2263 if (scoreboard_selected_team)
2264 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2266 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2267 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);
2269 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2270 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2271 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2275 float tl = STAT(TIMELIMIT);
2276 float fl = STAT(FRAGLIMIT);
2277 float ll = STAT(LEADLIMIT);
2278 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2281 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2282 if(!gametype.m_hidelimits)
2287 str = strcat(str, "^7 / "); // delimiter
2290 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2291 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2292 (teamscores_label(ts_primary) == "fastest") ? "" :
2293 TranslateScoresLabel(teamscores_label(ts_primary))));
2297 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2298 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2299 (scores_label(ps_primary) == "fastest") ? "" :
2300 TranslateScoresLabel(scores_label(ps_primary))));
2305 if(tl > 0 || fl > 0)
2308 if (ll_and_fl && fl > 0)
2309 str = strcat(str, "^7 & ");
2311 str = strcat(str, "^7 / ");
2316 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2317 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2318 (teamscores_label(ts_primary) == "fastest") ? "" :
2319 TranslateScoresLabel(teamscores_label(ts_primary))));
2323 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2324 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2325 (scores_label(ps_primary) == "fastest") ? "" :
2326 TranslateScoresLabel(scores_label(ps_primary))));
2330 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
2332 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2333 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2335 // End of Game Info Section
2337 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2338 if(panel.current_panel_bg != "0")
2339 pos.y += panel_bg_border;
2341 // Draw the scoreboard
2342 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2345 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2349 vector panel_bg_color_save = panel_bg_color;
2350 vector team_score_baseoffset;
2351 vector team_size_baseoffset;
2352 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2354 // put team score to the left of scoreboard (and team size to the right)
2355 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2356 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2357 if(panel.current_panel_bg != "0")
2359 team_score_baseoffset.x -= panel_bg_border;
2360 team_size_baseoffset.x += panel_bg_border;
2365 // put team score to the right of scoreboard (and team size to the left)
2366 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2367 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2368 if(panel.current_panel_bg != "0")
2370 team_score_baseoffset.x += panel_bg_border;
2371 team_size_baseoffset.x -= panel_bg_border;
2375 int team_size_total = 0;
2376 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2378 // calculate team size total (sum of all team sizes)
2379 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2380 if(tm.team != NUM_SPECTATOR)
2381 team_size_total += tm.team_size;
2384 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2386 if(tm.team == NUM_SPECTATOR)
2391 draw_beginBoldFont();
2392 vector rgb = Team_ColorRGB(tm.team);
2393 str = ftos(tm.(teamscores(ts_primary)));
2394 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2396 // team score on the left (default)
2397 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2401 // team score on the right
2402 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2404 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2406 // team size (if set to show on the side)
2407 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2409 // calculate the starting position for the whole team size info string
2410 str = sprintf("%d/%d", tm.team_size, team_size_total);
2411 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2413 // team size on the left
2414 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2418 // team size on the right
2419 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2421 str = sprintf("%d", tm.team_size);
2422 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2423 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2424 str = sprintf("/%d", team_size_total);
2425 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2429 // secondary score, e.g. keyhunt
2430 if(ts_primary != ts_secondary)
2432 str = ftos(tm.(teamscores(ts_secondary)));
2433 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2436 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2441 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2444 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2447 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2448 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2449 else if(panel_bg_color_team > 0)
2450 panel_bg_color = rgb * panel_bg_color_team;
2452 panel_bg_color = rgb;
2453 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2455 panel_bg_color = panel_bg_color_save;
2459 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2460 if(tm.team != NUM_SPECTATOR)
2463 // display it anyway
2464 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2467 // draw scoreboard spectators before accuracy and item stats
2468 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2469 pos = Scoreboard_Spectators_Draw(pos);
2472 // draw accuracy and item stats
2473 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2474 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2475 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2476 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2478 // draw scoreboard spectators after accuracy and item stats and before rankings
2479 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2480 pos = Scoreboard_Spectators_Draw(pos);
2483 if(MUTATOR_CALLHOOK(ShowRankings)) {
2484 string ranktitle = M_ARGV(0, string);
2485 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2486 if(race_speedaward_alltimebest)
2489 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2493 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2494 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2495 str = strcat(str, " / ");
2497 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2498 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2499 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2500 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2502 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2507 // draw scoreboard spectators after rankings
2508 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2509 pos = Scoreboard_Spectators_Draw(pos);
2512 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2514 // draw scoreboard spectators after mapstats
2515 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2516 pos = Scoreboard_Spectators_Draw(pos);
2520 // print information about respawn status
2521 float respawn_time = STAT(RESPAWN_TIME);
2522 if(!intermission && respawn_time)
2524 if(respawn_time < 0)
2526 // a negative number means we are awaiting respawn, time value is still the same
2527 respawn_time *= -1; // remove mark now that we checked it
2529 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2530 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2532 str = sprintf(_("^1Respawning in ^3%s^1..."),
2533 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2534 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2536 count_seconds(ceil(respawn_time - time))
2540 else if(time < respawn_time)
2542 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2543 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2544 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2546 count_seconds(ceil(respawn_time - time))
2550 else if(time >= respawn_time)
2551 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2553 pos.y += 1.2 * hud_fontsize.y;
2554 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2557 pos.y += hud_fontsize.y;
2558 if (scoreboard_fade_alpha < 1)
2559 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2560 else if (pos.y != scoreboard_bottom)
2562 if (pos.y > scoreboard_bottom)
2563 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2565 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2570 if (scoreboard_fade_alpha == 1)
2572 if (scoreboard_bottom > 0.95 * vid_conheight)
2573 rankings_rows = max(1, rankings_rows - 1);
2574 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2575 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2577 rankings_cnt = rankings_rows * rankings_columns;