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 void Scoreboard_InitScores()
506 ps_primary = ps_secondary = NULL;
507 ts_primary = ts_secondary = -1;
508 FOREACH(Scores, true, {
509 if(scores_flags(it) & SFL_NOT_SORTABLE)
511 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
512 if(f == SFL_SORT_PRIO_PRIMARY)
514 if(f == SFL_SORT_PRIO_SECONDARY)
517 if(ps_secondary == NULL)
518 ps_secondary = ps_primary;
520 for(i = 0; i < MAX_TEAMSCORE; ++i)
522 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
523 if(f == SFL_SORT_PRIO_PRIMARY)
525 if(f == SFL_SORT_PRIO_SECONDARY)
528 if(ts_secondary == -1)
529 ts_secondary = ts_primary;
531 Cmd_Scoreboard_SetFields(0);
535 void Scoreboard_UpdatePlayerTeams()
537 static float update_time;
538 if (time <= update_time)
545 for(pl = players.sort_next; pl; pl = pl.sort_next)
547 numplayers += pl.team != NUM_SPECTATOR;
549 int Team = entcs_GetScoreTeam(pl.sv_entnum);
550 if(SetTeam(pl, Team))
553 Scoreboard_UpdatePlayerPos(pl);
557 pl = players.sort_next;
562 print(strcat("PNUM: ", ftos(num), "\n"));
567 int Scoreboard_CompareScore(int vl, int vr, int f)
569 TC(int, vl); TC(int, vr); TC(int, f);
570 if(f & SFL_ZERO_IS_WORST)
572 if(vl == 0 && vr != 0)
574 if(vl != 0 && vr == 0)
578 return IS_INCREASING(f);
580 return IS_DECREASING(f);
584 float Scoreboard_ComparePlayerScores(entity left, entity right)
586 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
587 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
594 if(vl == NUM_SPECTATOR)
596 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
598 if(!left.gotscores && right.gotscores)
603 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
604 if (res >= 0) return res;
606 if (ps_secondary && ps_secondary != ps_primary)
608 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
609 if (res >= 0) return res;
612 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
613 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
614 if (res >= 0) return res;
617 if (left.sv_entnum < right.sv_entnum)
623 void Scoreboard_UpdatePlayerPos(entity player)
626 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
628 SORT_SWAP(player, ent);
630 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
632 SORT_SWAP(ent, player);
636 float Scoreboard_CompareTeamScores(entity left, entity right)
638 if(left.team == NUM_SPECTATOR)
640 if(right.team == NUM_SPECTATOR)
645 for(int i = -2; i < MAX_TEAMSCORE; ++i)
649 if (fld_idx == -1) fld_idx = ts_primary;
650 else if (ts_secondary == ts_primary) continue;
651 else fld_idx = ts_secondary;
656 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
659 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
660 if (r >= 0) return r;
663 if (left.team < right.team)
669 void Scoreboard_UpdateTeamPos(entity Team)
672 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
674 SORT_SWAP(Team, ent);
676 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
678 SORT_SWAP(ent, Team);
682 void Cmd_Scoreboard_Help()
684 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
685 LOG_HELP(_("Usage:"));
686 LOG_HELP("^2scoreboard_columns_set ^3default");
687 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
688 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
689 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
690 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
691 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
692 LOG_HELP(_("The following field names are recognized (case insensitive):"));
698 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
699 "of game types, then a slash, to make the field show up only in these\n"
700 "or in all but these game types. You can also specify 'all' as a\n"
701 "field to show all fields available for the current game mode."));
704 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
705 "include/exclude ALL teams/noteams game modes."));
708 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
709 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
710 "right of the vertical bar aligned to the right."));
711 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
712 "other gamemodes except DM."));
715 // NOTE: adding a gametype with ? to not warn for an optional field
716 // make sure it's excluded in a previous exclusive rule, if any
717 // otherwise the previous exclusive rule warns anyway
718 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
719 #define SCOREBOARD_DEFAULT_COLUMNS \
720 "ping pl fps name |" \
721 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
722 " -teams,lms/deaths +ft,tdm/deaths" \
724 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
725 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
726 " +tdm,ft,dom,ons,as/teamkills"\
727 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
728 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
729 " +lms/lives +lms/rank" \
730 " +kh/kckills +kh/losses +kh/caps" \
731 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
732 " +as/objectives +nb/faults +nb/goals" \
733 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
734 " +dom/ticks +dom/takes" \
735 " -lms,rc,cts,inv,nb/score"
737 void Cmd_Scoreboard_SetFields(int argc)
742 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
746 return; // do nothing, we don't know gametype and scores yet
748 // sbt_fields uses strunzone on the titles!
749 if(!sbt_field_title[0])
750 for(i = 0; i < MAX_SBT_FIELDS; ++i)
751 sbt_field_title[i] = strzone("(null)");
753 // TODO: re enable with gametype dependant cvars?
754 if(argc < 3) // no arguments provided
755 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
758 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
762 if(argv(2) == "default" || argv(2) == "expand_default")
764 if(argv(2) == "expand_default")
765 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
766 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
768 else if(argv(2) == "all" || argv(2) == "ALL")
770 string s = "ping pl name |"; // scores without label (not really scores)
773 // scores without label
774 s = strcat(s, " ", "sum");
775 s = strcat(s, " ", "kdratio");
776 s = strcat(s, " ", "frags");
778 FOREACH(Scores, true, {
780 if(it != ps_secondary)
781 if(scores_label(it) != "")
782 s = strcat(s, " ", scores_label(it));
784 if(ps_secondary != ps_primary)
785 s = strcat(s, " ", scores_label(ps_secondary));
786 s = strcat(s, " ", scores_label(ps_primary));
787 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
794 hud_fontsize = HUD_GetFontsize("hud_fontsize");
796 for(i = 1; i < argc - 1; ++i)
799 bool nocomplain = false;
800 if(substring(str, 0, 1) == "?")
803 str = substring(str, 1, strlen(str) - 1);
806 slash = strstrofs(str, "/", 0);
809 pattern = substring(str, 0, slash);
810 str = substring(str, slash + 1, strlen(str) - (slash + 1));
812 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
816 str = strtolower(str);
817 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
818 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
823 // fields without a label (not networked via the score system)
824 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
825 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
826 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
827 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
828 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
829 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
830 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
831 default: // fields with a label
833 // map alternative labels
834 if (str == "damage") str = "dmg";
835 if (str == "damagetaken") str = "dmgtaken";
837 FOREACH(Scores, true, {
838 if (str == strtolower(scores_label(it))) {
840 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
844 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
845 if(!nocomplain && str != "fps") // server can disable the fps field
846 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
848 strfree(sbt_field_title[sbt_num_fields]);
849 sbt_field_size[sbt_num_fields] = 0;
853 sbt_field[sbt_num_fields] = j;
856 if(j == ps_secondary)
857 have_secondary = true;
862 if(sbt_num_fields >= MAX_SBT_FIELDS)
866 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
868 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
869 have_secondary = true;
870 if(ps_primary == ps_secondary)
871 have_secondary = true;
872 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
874 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
878 strfree(sbt_field_title[sbt_num_fields]);
879 for(i = sbt_num_fields; i > 0; --i)
881 sbt_field_title[i] = sbt_field_title[i-1];
882 sbt_field_size[i] = sbt_field_size[i-1];
883 sbt_field[i] = sbt_field[i-1];
885 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
886 sbt_field[0] = SP_NAME;
888 LOG_INFO("fixed missing field 'name'");
892 strfree(sbt_field_title[sbt_num_fields]);
893 for(i = sbt_num_fields; i > 1; --i)
895 sbt_field_title[i] = sbt_field_title[i-1];
896 sbt_field_size[i] = sbt_field_size[i-1];
897 sbt_field[i] = sbt_field[i-1];
899 sbt_field_title[1] = strzone("|");
900 sbt_field[1] = SP_SEPARATOR;
901 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
903 LOG_INFO("fixed missing field '|'");
906 else if(!have_separator)
908 strcpy(sbt_field_title[sbt_num_fields], "|");
909 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
910 sbt_field[sbt_num_fields] = SP_SEPARATOR;
912 LOG_INFO("fixed missing field '|'");
916 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
917 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
918 sbt_field[sbt_num_fields] = ps_secondary;
920 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
924 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
925 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
926 sbt_field[sbt_num_fields] = ps_primary;
928 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
932 sbt_field[sbt_num_fields] = SP_END;
935 string Scoreboard_AddPlayerId(string pl_name, entity pl)
937 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
938 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
939 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
943 vector sbt_field_rgb;
944 string sbt_field_icon0;
945 string sbt_field_icon1;
946 string sbt_field_icon2;
947 vector sbt_field_icon0_rgb;
948 vector sbt_field_icon1_rgb;
949 vector sbt_field_icon2_rgb;
950 string Scoreboard_GetName(entity pl)
952 if(ready_waiting && pl.ready)
954 sbt_field_icon0 = "gfx/scoreboard/player_ready";
958 int f = entcs_GetClientColors(pl.sv_entnum);
960 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
961 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
962 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
963 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
964 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
967 return entcs_GetName(pl.sv_entnum);
970 string Scoreboard_GetField(entity pl, PlayerScoreField field)
972 float tmp, num, denom;
975 sbt_field_rgb = '1 1 1';
976 sbt_field_icon0 = "";
977 sbt_field_icon1 = "";
978 sbt_field_icon2 = "";
979 sbt_field_icon0_rgb = '1 1 1';
980 sbt_field_icon1_rgb = '1 1 1';
981 sbt_field_icon2_rgb = '1 1 1';
986 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
987 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
991 tmp = max(0, min(220, f-80)) / 220;
992 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
998 f = pl.ping_packetloss;
999 tmp = pl.ping_movementloss;
1000 if(f == 0 && tmp == 0)
1002 str = ftos(ceil(f * 100));
1004 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1005 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1006 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1010 str = Scoreboard_GetName(pl);
1011 if (autocvar_hud_panel_scoreboard_playerid)
1012 str = Scoreboard_AddPlayerId(str, pl);
1016 f = pl.(scores(SP_KILLS));
1017 f -= pl.(scores(SP_SUICIDES));
1021 num = pl.(scores(SP_KILLS));
1022 denom = pl.(scores(SP_DEATHS));
1025 sbt_field_rgb = '0 1 0';
1026 str = sprintf("%d", num);
1027 } else if(num <= 0) {
1028 sbt_field_rgb = '1 0 0';
1029 str = sprintf("%.1f", num/denom);
1031 str = sprintf("%.1f", num/denom);
1035 f = pl.(scores(SP_KILLS));
1036 f -= pl.(scores(SP_DEATHS));
1039 sbt_field_rgb = '0 1 0';
1041 sbt_field_rgb = '1 1 1';
1043 sbt_field_rgb = '1 0 0';
1049 float elo = pl.(scores(SP_ELO));
1051 case -1: return "...";
1052 case -2: return _("N/A");
1053 default: return ftos(elo);
1059 float fps = pl.(scores(SP_FPS));
1062 sbt_field_rgb = '1 1 1';
1063 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1065 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1066 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1070 case SP_DMG: case SP_DMGTAKEN:
1071 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1073 default: case SP_SCORE:
1074 tmp = pl.(scores(field));
1075 f = scores_flags(field);
1076 if(field == ps_primary)
1077 sbt_field_rgb = '1 1 0';
1078 else if(field == ps_secondary)
1079 sbt_field_rgb = '0 1 1';
1081 sbt_field_rgb = '1 1 1';
1082 return ScoreString(f, tmp);
1087 float sbt_fixcolumnwidth_len;
1088 float sbt_fixcolumnwidth_iconlen;
1089 float sbt_fixcolumnwidth_marginlen;
1091 string Scoreboard_FixColumnWidth(int i, string str)
1097 sbt_fixcolumnwidth_iconlen = 0;
1099 if(sbt_field_icon0 != "")
1101 sz = draw_getimagesize(sbt_field_icon0);
1103 if(sbt_fixcolumnwidth_iconlen < f)
1104 sbt_fixcolumnwidth_iconlen = f;
1107 if(sbt_field_icon1 != "")
1109 sz = draw_getimagesize(sbt_field_icon1);
1111 if(sbt_fixcolumnwidth_iconlen < f)
1112 sbt_fixcolumnwidth_iconlen = f;
1115 if(sbt_field_icon2 != "")
1117 sz = draw_getimagesize(sbt_field_icon2);
1119 if(sbt_fixcolumnwidth_iconlen < f)
1120 sbt_fixcolumnwidth_iconlen = f;
1123 if(sbt_fixcolumnwidth_iconlen != 0)
1125 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1126 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1129 sbt_fixcolumnwidth_marginlen = 0;
1131 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1134 float remaining_space = 0;
1135 for(j = 0; j < sbt_num_fields; ++j)
1137 if (sbt_field[i] != SP_SEPARATOR)
1138 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1139 sbt_field_size[i] = panel_size.x - remaining_space;
1141 if (sbt_fixcolumnwidth_iconlen != 0)
1142 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1143 float namesize = panel_size.x - remaining_space;
1144 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1145 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1147 max_namesize = vid_conwidth - remaining_space;
1150 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1152 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1153 if(sbt_field_size[i] < f)
1154 sbt_field_size[i] = f;
1159 void Scoreboard_initFieldSizes()
1161 for(int i = 0; i < sbt_num_fields; ++i)
1163 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1164 Scoreboard_FixColumnWidth(i, "");
1168 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1171 vector column_dim = eY * panel_size.y;
1173 column_dim.y -= 1.25 * hud_fontsize.y;
1174 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1175 pos.x += hud_fontsize.x * 0.5;
1176 for(i = 0; i < sbt_num_fields; ++i)
1178 if(sbt_field[i] == SP_SEPARATOR)
1180 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1183 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1184 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1185 pos.x += column_dim.x;
1187 if(sbt_field[i] == SP_SEPARATOR)
1189 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1190 for(i = sbt_num_fields - 1; i > 0; --i)
1192 if(sbt_field[i] == SP_SEPARATOR)
1195 pos.x -= sbt_field_size[i];
1200 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1201 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1204 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1205 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1206 pos.x -= hud_fontsize.x;
1210 pos.x = panel_pos.x;
1211 pos.y += 1.25 * hud_fontsize.y;
1215 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1217 TC(bool, is_self); TC(int, pl_number);
1219 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1221 vector h_pos = item_pos;
1222 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1223 // alternated rows highlighting
1224 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1226 if (pl == scoreboard_selected_player)
1227 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1230 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1231 else if((sbt_highlight) && (!(pl_number % 2)))
1232 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1234 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1236 vector pos = item_pos;
1237 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1239 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1241 pos.x += hud_fontsize.x * 0.5;
1242 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1243 vector tmp = '0 0 0';
1245 PlayerScoreField field;
1246 for(i = 0; i < sbt_num_fields; ++i)
1248 field = sbt_field[i];
1249 if(field == SP_SEPARATOR)
1252 if(is_spec && field != SP_NAME && field != SP_PING) {
1253 pos.x += sbt_field_size[i] + hud_fontsize.x;
1256 str = Scoreboard_GetField(pl, field);
1257 str = Scoreboard_FixColumnWidth(i, str);
1259 pos.x += sbt_field_size[i] + hud_fontsize.x;
1261 if(field == SP_NAME) {
1262 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1263 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1265 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1266 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1269 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1270 if(sbt_field_icon0 != "")
1271 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1272 if(sbt_field_icon1 != "")
1273 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1274 if(sbt_field_icon2 != "")
1275 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1278 if(sbt_field[i] == SP_SEPARATOR)
1280 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1281 for(i = sbt_num_fields-1; i > 0; --i)
1283 field = sbt_field[i];
1284 if(field == SP_SEPARATOR)
1287 if(is_spec && field != SP_NAME && field != SP_PING) {
1288 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1292 str = Scoreboard_GetField(pl, field);
1293 str = Scoreboard_FixColumnWidth(i, str);
1295 if(field == SP_NAME) {
1296 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1297 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1299 tmp.x = sbt_fixcolumnwidth_len;
1300 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1303 tmp.x = sbt_field_size[i];
1304 if(sbt_field_icon0 != "")
1305 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1306 if(sbt_field_icon1 != "")
1307 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1308 if(sbt_field_icon2 != "")
1309 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1310 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1315 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1318 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1321 vector h_pos = item_pos;
1322 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1324 bool complete = (this_team == NUM_SPECTATOR);
1327 if((sbt_highlight) && (!(pl_number % 2)))
1328 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1330 vector pos = item_pos;
1331 pos.x += hud_fontsize.x * 0.5;
1332 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1334 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1336 width_limit -= stringwidth("...", false, hud_fontsize);
1337 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1338 static float max_name_width = 0;
1340 float fieldsize = 0;
1341 float min_fieldsize = 0;
1342 float fieldpadding = hud_fontsize.x * 0.25;
1343 if(this_team == NUM_SPECTATOR)
1345 if(autocvar_hud_panel_scoreboard_spectators_showping)
1346 min_fieldsize = stringwidth("999", false, hud_fontsize);
1348 else if(autocvar_hud_panel_scoreboard_others_showscore)
1349 min_fieldsize = stringwidth("99", false, hud_fontsize);
1350 for(i = 0; pl; pl = pl.sort_next)
1352 if(pl.team != this_team)
1354 if(pl == ignored_pl)
1358 if(this_team == NUM_SPECTATOR)
1360 if(autocvar_hud_panel_scoreboard_spectators_showping)
1361 field = Scoreboard_GetField(pl, SP_PING);
1363 else if(autocvar_hud_panel_scoreboard_others_showscore)
1364 field = Scoreboard_GetField(pl, SP_SCORE);
1366 string str = entcs_GetName(pl.sv_entnum);
1367 if (autocvar_hud_panel_scoreboard_playerid)
1368 str = Scoreboard_AddPlayerId(str, pl);
1369 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1370 float column_width = stringwidth(str, true, hud_fontsize);
1371 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1373 if(column_width > max_name_width)
1374 max_name_width = column_width;
1375 column_width = max_name_width;
1379 fieldsize = stringwidth(field, false, hud_fontsize);
1380 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1383 if(pos.x + column_width > width_limit)
1388 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1393 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1394 pos.y += hud_fontsize.y * 1.25;
1398 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1400 if (pl == scoreboard_selected_player)
1402 h_size.x = column_width + hud_fontsize.x * 0.25;
1403 h_size.y = hud_fontsize.y;
1404 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1408 vector name_pos = pos;
1409 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1410 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1411 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1414 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1415 h_size.y = hud_fontsize.y;
1416 vector field_pos = pos;
1417 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1418 field_pos.x += column_width - h_size.x;
1420 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1421 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1422 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1426 h_size.x = column_width + hud_fontsize.x * 0.25;
1427 h_size.y = hud_fontsize.y;
1428 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1430 pos.x += column_width;
1431 pos.x += hud_fontsize.x;
1433 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1436 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1438 int max_players = 999;
1439 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1441 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1444 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1445 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1446 height /= team_count;
1449 height -= panel_bg_padding * 2; // - padding
1450 max_players = floor(height / (hud_fontsize.y * 1.25));
1451 if(max_players <= 1)
1453 if(max_players == tm.team_size)
1458 entity me = playerslots[current_player];
1460 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1461 panel_size.y += panel_bg_padding * 2;
1463 vector scoreboard_selected_hl_pos = pos;
1464 vector scoreboard_selected_hl_size = '0 0 0';
1465 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1466 scoreboard_selected_hl_size.y = panel_size.y;
1470 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1471 if(panel.current_panel_bg != "0")
1472 end_pos.y += panel_bg_border * 2;
1474 if(panel_bg_padding)
1476 panel_pos += '1 1 0' * panel_bg_padding;
1477 panel_size -= '2 2 0' * panel_bg_padding;
1481 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1485 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1487 pos.y += 1.25 * hud_fontsize.y;
1490 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1492 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1495 // print header row and highlight columns
1496 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1498 // fill the table and draw the rows
1499 bool is_self = false;
1500 bool self_shown = false;
1502 for(pl = players.sort_next; pl; pl = pl.sort_next)
1504 if(pl.team != tm.team)
1506 if(i == max_players - 2 && pl != me)
1508 if(!self_shown && me.team == tm.team)
1510 Scoreboard_DrawItem(pos, rgb, me, true, i);
1512 pos.y += 1.25 * hud_fontsize.y;
1516 if(i >= max_players - 1)
1518 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1521 is_self = (pl.sv_entnum == current_player);
1522 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1525 pos.y += 1.25 * hud_fontsize.y;
1529 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1531 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1533 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1534 _alpha *= panel_fg_alpha;
1536 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1540 panel_size.x += panel_bg_padding * 2; // restore initial width
1544 bool Scoreboard_WouldDraw()
1546 if (scoreboard_ui_enabled)
1548 if (scoreboard_ui_disabling)
1550 if (scoreboard_fade_alpha == 0)
1551 HUD_Scoreboard_UI_Disable_Instantly();
1554 if (intermission && scoreboard_ui_enabled == 2)
1556 HUD_Scoreboard_UI_Disable_Instantly();
1561 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1563 else if (QuickMenu_IsOpened())
1565 else if (HUD_Radar_Clickable())
1567 else if (scoreboard_showscores)
1569 else if (intermission == 1)
1571 else if (intermission == 2)
1573 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1574 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1578 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1583 float average_accuracy;
1584 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1586 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1588 WepSet weapons_stat = WepSet_GetFromStat();
1589 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1590 int disownedcnt = 0;
1592 FOREACH(Weapons, it != WEP_Null, {
1593 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1595 WepSet set = it.m_wepset;
1596 if(it.spawnflags & WEP_TYPE_OTHER)
1601 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1603 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1610 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1611 if (weapon_cnt <= 0) return pos;
1614 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1616 int columns = ceil(weapon_cnt / rows);
1618 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1619 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1620 float height = weapon_height + hud_fontsize.y;
1622 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);
1623 pos.y += 1.25 * hud_fontsize.y;
1624 if(panel.current_panel_bg != "0")
1625 pos.y += panel_bg_border;
1628 panel_size.y = height * rows;
1629 panel_size.y += panel_bg_padding * 2;
1631 float panel_bg_alpha_save = panel_bg_alpha;
1632 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1634 panel_bg_alpha = panel_bg_alpha_save;
1636 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1637 if(panel.current_panel_bg != "0")
1638 end_pos.y += panel_bg_border * 2;
1640 if(panel_bg_padding)
1642 panel_pos += '1 1 0' * panel_bg_padding;
1643 panel_size -= '2 2 0' * panel_bg_padding;
1647 vector tmp = panel_size;
1649 float weapon_width = tmp.x / columns / rows;
1652 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1656 // column highlighting
1657 for (int i = 0; i < columns; ++i)
1659 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);
1662 for (int i = 0; i < rows; ++i)
1663 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1666 average_accuracy = 0;
1667 int weapons_with_stats = 0;
1669 pos.x += weapon_width / 2;
1671 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1674 Accuracy_LoadColors();
1676 float oldposx = pos.x;
1680 FOREACH(Weapons, it != WEP_Null, {
1681 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1683 WepSet set = it.m_wepset;
1684 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1686 if (it.spawnflags & WEP_TYPE_OTHER)
1690 if (weapon_stats >= 0)
1691 weapon_alpha = sbt_fg_alpha;
1693 weapon_alpha = 0.2 * sbt_fg_alpha;
1696 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1698 if (weapon_stats >= 0) {
1699 weapons_with_stats += 1;
1700 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1702 string s = sprintf("%d%%", weapon_stats * 100);
1703 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1705 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1706 rgb = Accuracy_GetColor(weapon_stats);
1708 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1710 tmpos.x += weapon_width * rows;
1711 pos.x += weapon_width * rows;
1712 if (rows == 2 && column == columns - 1) {
1720 if (weapons_with_stats)
1721 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1723 panel_size.x += panel_bg_padding * 2; // restore initial width
1728 bool is_item_filtered(entity it)
1730 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1732 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1735 if (it.instanceOfArmor || it.instanceOfHealth)
1737 int ha_mask = floor(mask) % 10;
1740 default: return false;
1741 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1742 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1743 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1744 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1747 if (it.instanceOfAmmo)
1749 int ammo_mask = floor(mask / 10) % 10;
1750 return (ammo_mask == 1);
1755 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1757 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1759 int disowned_cnt = 0;
1760 int uninteresting_cnt = 0;
1761 IL_EACH(default_order_items, true, {
1762 int q = g_inventory.inv_items[it.m_id];
1763 //q = 1; // debug: display all items
1764 if (is_item_filtered(it))
1765 ++uninteresting_cnt;
1769 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1770 int n = items_cnt - disowned_cnt;
1771 if (n <= 0) return pos;
1773 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1774 int columns = max(6, ceil(n / rows));
1776 float item_height = hud_fontsize.y * 2.3;
1777 float height = item_height + hud_fontsize.y;
1779 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1780 pos.y += 1.25 * hud_fontsize.y;
1781 if(panel.current_panel_bg != "0")
1782 pos.y += panel_bg_border;
1785 panel_size.y = height * rows;
1786 panel_size.y += panel_bg_padding * 2;
1788 float panel_bg_alpha_save = panel_bg_alpha;
1789 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1791 panel_bg_alpha = panel_bg_alpha_save;
1793 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1794 if(panel.current_panel_bg != "0")
1795 end_pos.y += panel_bg_border * 2;
1797 if(panel_bg_padding)
1799 panel_pos += '1 1 0' * panel_bg_padding;
1800 panel_size -= '2 2 0' * panel_bg_padding;
1804 vector tmp = panel_size;
1806 float item_width = tmp.x / columns / rows;
1809 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1813 // column highlighting
1814 for (int i = 0; i < columns; ++i)
1816 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);
1819 for (int i = 0; i < rows; ++i)
1820 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1824 pos.x += item_width / 2;
1826 float oldposx = pos.x;
1830 IL_EACH(default_order_items, !is_item_filtered(it), {
1831 int n = g_inventory.inv_items[it.m_id];
1832 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1833 if (n <= 0) continue;
1834 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);
1836 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1837 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1838 tmpos.x += item_width * rows;
1839 pos.x += item_width * rows;
1840 if (rows == 2 && column == columns - 1) {
1848 panel_size.x += panel_bg_padding * 2; // restore initial width
1853 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1855 pos.x += hud_fontsize.x * 0.25;
1856 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1857 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1858 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1860 pos.y += hud_fontsize.y;
1865 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1866 float stat_secrets_found, stat_secrets_total;
1867 float stat_monsters_killed, stat_monsters_total;
1871 // get monster stats
1872 stat_monsters_killed = STAT(MONSTERS_KILLED);
1873 stat_monsters_total = STAT(MONSTERS_TOTAL);
1875 // get secrets stats
1876 stat_secrets_found = STAT(SECRETS_FOUND);
1877 stat_secrets_total = STAT(SECRETS_TOTAL);
1879 // get number of rows
1880 if(stat_secrets_total)
1882 if(stat_monsters_total)
1885 // if no rows, return
1889 // draw table header
1890 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1891 pos.y += 1.25 * hud_fontsize.y;
1892 if(panel.current_panel_bg != "0")
1893 pos.y += panel_bg_border;
1896 panel_size.y = hud_fontsize.y * rows;
1897 panel_size.y += panel_bg_padding * 2;
1900 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1901 if(panel.current_panel_bg != "0")
1902 end_pos.y += panel_bg_border * 2;
1904 if(panel_bg_padding)
1906 panel_pos += '1 1 0' * panel_bg_padding;
1907 panel_size -= '2 2 0' * panel_bg_padding;
1911 vector tmp = panel_size;
1914 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1917 if(stat_monsters_total)
1919 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1920 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1924 if(stat_secrets_total)
1926 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1927 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1930 panel_size.x += panel_bg_padding * 2; // restore initial width
1934 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1937 RANKINGS_RECEIVED_CNT = 0;
1938 for (i=RANKINGS_CNT-1; i>=0; --i)
1940 ++RANKINGS_RECEIVED_CNT;
1942 if (RANKINGS_RECEIVED_CNT == 0)
1945 vector hl_rgb = rgb + '0.5 0.5 0.5';
1947 vector scoreboard_selected_hl_pos = pos;
1949 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1950 pos.y += 1.25 * hud_fontsize.y;
1951 if(panel.current_panel_bg != "0")
1952 pos.y += panel_bg_border;
1954 vector scoreboard_selected_hl_size = '0 0 0';
1955 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1956 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1961 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1963 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1968 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1970 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1974 float ranksize = 3 * hud_fontsize.x;
1975 float timesize = 5 * hud_fontsize.x;
1976 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1977 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1978 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1981 rankings_cnt = RANKINGS_RECEIVED_CNT;
1982 rankings_rows = ceil(rankings_cnt / rankings_columns);
1985 // expand name column to fill the entire row
1986 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1987 namesize += available_space;
1988 columnsize.x += available_space;
1990 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1991 panel_size.y += panel_bg_padding * 2;
1992 scoreboard_selected_hl_size.y += panel_size.y;
1996 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1997 if(panel.current_panel_bg != "0")
1998 end_pos.y += panel_bg_border * 2;
2000 if(panel_bg_padding)
2002 panel_pos += '1 1 0' * panel_bg_padding;
2003 panel_size -= '2 2 0' * panel_bg_padding;
2009 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2011 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2013 int column = 0, j = 0;
2014 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2015 int start_item = rankings_start_column * rankings_rows;
2016 for(i = start_item; i < start_item + rankings_cnt; ++i)
2018 int t = grecordtime[i];
2022 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2023 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2024 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2025 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2027 str = count_ordinal(i+1);
2028 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2029 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2030 str = ColorTranslateRGB(grecordholder[i]);
2032 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2033 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2035 pos.y += 1.25 * hud_fontsize.y;
2037 if(j >= rankings_rows)
2041 pos.x += panel_size.x / rankings_columns;
2042 pos.y = panel_pos.y;
2045 strfree(zoned_name_self);
2047 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2049 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2050 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2053 panel_size.x += panel_bg_padding * 2; // restore initial width
2057 bool have_weapon_stats;
2058 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2060 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2062 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2065 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2066 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2072 if (!have_weapon_stats)
2074 FOREACH(Weapons, it != WEP_Null, {
2075 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2076 if (weapon_stats >= 0)
2078 have_weapon_stats = true;
2082 if (!have_weapon_stats)
2089 bool have_item_stats;
2090 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2092 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2094 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2097 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2098 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2104 if (!have_item_stats)
2106 IL_EACH(default_order_items, true, {
2107 if (!is_item_filtered(it))
2109 int q = g_inventory.inv_items[it.m_id];
2110 //q = 1; // debug: display all items
2113 have_item_stats = true;
2118 if (!have_item_stats)
2125 vector Scoreboard_Spectators_Draw(vector pos) {
2130 for(pl = players.sort_next; pl; pl = pl.sort_next)
2132 if(pl.team == NUM_SPECTATOR)
2134 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2135 if(tm.team == NUM_SPECTATOR)
2137 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2138 draw_beginBoldFont();
2139 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2141 pos.y += 1.25 * hud_fontsize.y;
2143 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2144 pos.y += 1.25 * hud_fontsize.y;
2149 if (str != "") // if there's at least one spectator
2150 pos.y += 0.5 * hud_fontsize.y;
2155 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2157 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2158 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2159 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit),
2160 (s_label == "score") ? CTX(_("SCO^points")) :
2161 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2164 void Scoreboard_Draw()
2166 if(!autocvar__hud_configure)
2168 if(!hud_draw_maximized) return;
2170 // frametime checks allow to toggle the scoreboard even when the game is paused
2171 if(scoreboard_active) {
2172 if (scoreboard_fade_alpha == 0)
2173 scoreboard_time = time;
2174 if(hud_configure_menu_open == 1)
2175 scoreboard_fade_alpha = 1;
2176 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2177 if (scoreboard_fadeinspeed && frametime)
2178 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2180 scoreboard_fade_alpha = 1;
2181 if(hud_fontsize_str != autocvar_hud_fontsize)
2183 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2184 Scoreboard_initFieldSizes();
2185 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2189 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2190 if (scoreboard_fadeoutspeed && frametime)
2191 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2193 scoreboard_fade_alpha = 0;
2196 if (!scoreboard_fade_alpha)
2198 scoreboard_acc_fade_alpha = 0;
2199 scoreboard_itemstats_fade_alpha = 0;
2204 scoreboard_fade_alpha = 0;
2206 if (autocvar_hud_panel_scoreboard_dynamichud)
2209 HUD_Scale_Disable();
2211 if(scoreboard_fade_alpha <= 0)
2213 panel_fade_alpha *= scoreboard_fade_alpha;
2214 HUD_Panel_LoadCvars();
2216 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2217 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2218 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2219 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2220 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2221 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2222 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2224 // don't overlap with con_notify
2225 if(!autocvar__hud_configure)
2226 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2228 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2229 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2230 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2231 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2232 panel_pos.x = scoreboard_left;
2233 panel_size.x = fixed_scoreboard_width;
2235 Scoreboard_UpdatePlayerTeams();
2237 scoreboard_top = panel_pos.y;
2238 vector pos = panel_pos;
2243 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2245 // Begin of Game Info Section
2246 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2247 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2249 // Game Info: Game Type
2250 if (scoreboard_ui_enabled == 2)
2251 str = _("Team Selection");
2253 str = MapInfo_Type_ToText(gametype);
2254 draw_beginBoldFont();
2255 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);
2258 pos.y += sb_gameinfo_type_fontsize.y;
2259 // Game Info: Game Detail
2260 if (scoreboard_ui_enabled == 2)
2262 if (scoreboard_selected_team)
2263 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2265 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2266 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2268 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2269 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2270 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);
2274 float tl = STAT(TIMELIMIT);
2275 float fl = STAT(FRAGLIMIT);
2276 float ll = STAT(LEADLIMIT);
2277 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2280 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2281 if(!gametype.m_hidelimits)
2286 str = strcat(str, "^7 / "); // delimiter
2287 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2291 if(tl > 0 || fl > 0)
2294 if (ll_and_fl && fl > 0)
2295 str = strcat(str, "^7 & ");
2297 str = strcat(str, "^7 / ");
2299 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2302 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
2303 // map name and player count
2307 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2308 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2309 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2311 // End of Game Info Section
2313 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2314 if(panel.current_panel_bg != "0")
2315 pos.y += panel_bg_border;
2317 // Draw the scoreboard
2318 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2321 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2325 vector panel_bg_color_save = panel_bg_color;
2326 vector team_score_baseoffset;
2327 vector team_size_baseoffset;
2328 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2330 // put team score to the left of scoreboard (and team size to the right)
2331 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2332 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2333 if(panel.current_panel_bg != "0")
2335 team_score_baseoffset.x -= panel_bg_border;
2336 team_size_baseoffset.x += panel_bg_border;
2341 // put team score to the right of scoreboard (and team size to the left)
2342 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2343 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2344 if(panel.current_panel_bg != "0")
2346 team_score_baseoffset.x += panel_bg_border;
2347 team_size_baseoffset.x -= panel_bg_border;
2351 int team_size_total = 0;
2352 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2354 // calculate team size total (sum of all team sizes)
2355 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2356 if(tm.team != NUM_SPECTATOR)
2357 team_size_total += tm.team_size;
2360 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2362 if(tm.team == NUM_SPECTATOR)
2367 draw_beginBoldFont();
2368 vector rgb = Team_ColorRGB(tm.team);
2369 str = ftos(tm.(teamscores(ts_primary)));
2370 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2372 // team score on the left (default)
2373 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2377 // team score on the right
2378 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2380 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2382 // team size (if set to show on the side)
2383 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2385 // calculate the starting position for the whole team size info string
2386 str = sprintf("%d/%d", tm.team_size, team_size_total);
2387 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2389 // team size on the left
2390 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2394 // team size on the right
2395 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2397 str = sprintf("%d", tm.team_size);
2398 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2399 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2400 str = sprintf("/%d", team_size_total);
2401 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2405 // secondary score, e.g. keyhunt
2406 if(ts_primary != ts_secondary)
2408 str = ftos(tm.(teamscores(ts_secondary)));
2409 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2412 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2417 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2420 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2423 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2424 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2425 else if(panel_bg_color_team > 0)
2426 panel_bg_color = rgb * panel_bg_color_team;
2428 panel_bg_color = rgb;
2429 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2431 panel_bg_color = panel_bg_color_save;
2435 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2436 if(tm.team != NUM_SPECTATOR)
2439 // display it anyway
2440 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2443 // draw scoreboard spectators before accuracy and item stats
2444 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2445 pos = Scoreboard_Spectators_Draw(pos);
2448 // draw accuracy and item stats
2449 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2450 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2451 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2452 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2454 // draw scoreboard spectators after accuracy and item stats and before rankings
2455 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2456 pos = Scoreboard_Spectators_Draw(pos);
2459 if(MUTATOR_CALLHOOK(ShowRankings)) {
2460 string ranktitle = M_ARGV(0, string);
2461 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2462 if(race_speedaward_alltimebest)
2465 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2469 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2470 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2471 str = strcat(str, " / ");
2473 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2474 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2475 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2476 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2478 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2483 // draw scoreboard spectators after rankings
2484 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2485 pos = Scoreboard_Spectators_Draw(pos);
2488 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2490 // draw scoreboard spectators after mapstats
2491 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2492 pos = Scoreboard_Spectators_Draw(pos);
2496 // print information about respawn status
2497 float respawn_time = STAT(RESPAWN_TIME);
2498 if(!intermission && respawn_time)
2500 if(respawn_time < 0)
2502 // a negative number means we are awaiting respawn, time value is still the same
2503 respawn_time *= -1; // remove mark now that we checked it
2505 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2506 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2508 str = sprintf(_("^1Respawning in ^3%s^1..."),
2509 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2510 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2512 count_seconds(ceil(respawn_time - time))
2516 else if(time < respawn_time)
2518 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2519 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2520 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2522 count_seconds(ceil(respawn_time - time))
2526 else if(time >= respawn_time)
2527 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2529 pos.y += 1.2 * hud_fontsize.y;
2530 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2533 pos.y += hud_fontsize.y;
2534 if (scoreboard_fade_alpha < 1)
2535 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2536 else if (pos.y != scoreboard_bottom)
2538 if (pos.y > scoreboard_bottom)
2539 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2541 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2546 if (scoreboard_fade_alpha == 1)
2548 if (scoreboard_bottom > 0.95 * vid_conheight)
2549 rankings_rows = max(1, rankings_rows - 1);
2550 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2551 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2553 rankings_cnt = rankings_rows * rankings_columns;