1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
111 float scoreboard_time;
113 // mode 0: returns translated label
114 // mode 1: prints name and description of all the labels
115 string Label_getInfo(string label, int mode)
118 label = "bckills"; // first case in the switch
122 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
123 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
124 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
125 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
126 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
127 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
128 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
129 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
130 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
131 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
132 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
133 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
134 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
135 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
136 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
137 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
138 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
139 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
140 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
141 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
142 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
143 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
144 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
145 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
146 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
147 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
148 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
149 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
150 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
151 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
152 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
153 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
154 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
155 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
156 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
157 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
158 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
159 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
160 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
161 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
162 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
163 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
164 default: return label;
169 void HUD_Scoreboard_UI_Disable()
171 scoreboard_showscores = false;
172 scoreboard_ui_enabled = 0;
173 scoreboard_selected_panel = 0;
174 scoreboard_selected_player = NULL;
175 scoreboard_selected_team = NULL;
178 // mode: 0 normal, 1 team selection
179 void Scoreboard_UI_Enable(int mode)
183 if (scoreboard_ui_enabled == 2 || !teamplay)
186 // release player's pressed keys as they aren't released elsewhere
187 // in particular jump needs to be released as it may open the team selection
188 // (when server detects jump has been pressed it sends the command to open the team selection)
189 Release_Common_Keys();
190 scoreboard_ui_enabled = 2;
191 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
195 if (scoreboard_ui_enabled == 1)
197 scoreboard_ui_enabled = 1;
198 scoreboard_selected_panel = SB_PANEL_FIRST;
200 scoreboard_selected_player = NULL;
201 scoreboard_selected_team = NULL;
202 scoreboard_selected_panel_time = time;
205 int rankings_start_column;
206 int rankings_rows = 0;
207 int rankings_columns = 0;
208 int rankings_cnt = 0;
209 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
213 if(!scoreboard_ui_enabled)
218 mousepos.x = nPrimary;
219 mousepos.y = nSecondary;
226 // at this point bInputType can only be 0 or 1 (key pressed or released)
227 bool key_pressed = (bInputType == 0);
229 // ESC to exit (TAB-ESC works too)
230 if(nPrimary == K_ESCAPE)
234 HUD_Scoreboard_UI_Disable();
238 // block any input while a menu dialog is fading
239 if(autocvar__menu_alpha)
245 // allow console bind to work
246 string con_keys = findkeysforcommand("toggleconsole", 0);
247 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
249 bool hit_con_bind = false;
251 for (i = 0; i < keys; ++i)
253 if(nPrimary == stof(argv(i)))
258 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
259 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
260 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
261 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
264 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
265 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
266 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
267 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
270 if(nPrimary == K_TAB)
274 if (scoreboard_ui_enabled == 2)
275 goto downarrow_action;
277 ++scoreboard_selected_panel;
278 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
279 ++scoreboard_selected_panel;
280 if (scoreboard_selected_panel > SB_PANEL_MAX)
281 scoreboard_selected_panel = SB_PANEL_FIRST;
283 scoreboard_selected_panel_time = time;
285 else if(nPrimary == K_DOWNARROW)
289 LABEL(downarrow_action);
290 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
292 if (scoreboard_ui_enabled == 2)
294 entity curr_team = NULL;
295 bool scoreboard_selected_team_found = false;
296 if (!scoreboard_selected_team)
297 scoreboard_selected_team_found = true;
299 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
301 if(tm.team == NUM_SPECTATOR)
304 if (scoreboard_selected_team_found)
306 if (scoreboard_selected_team == tm)
307 scoreboard_selected_team_found = true;
310 if (curr_team == scoreboard_selected_team) // loop reached the last team
312 scoreboard_selected_team = curr_team;
317 entity curr_pl = NULL;
318 bool scoreboard_selected_player_found = false;
319 if (!scoreboard_selected_player)
320 scoreboard_selected_player_found = true;
322 for(tm = teams.sort_next; tm; tm = tm.sort_next)
324 if(tm.team != NUM_SPECTATOR)
325 for(pl = players.sort_next; pl; pl = pl.sort_next)
327 if(pl.team != tm.team)
330 if (scoreboard_selected_player_found)
332 if (scoreboard_selected_player == pl)
333 scoreboard_selected_player_found = true;
337 if (curr_pl == scoreboard_selected_player) // loop reached the last player
339 scoreboard_selected_player = curr_pl;
343 else if(nPrimary == K_UPARROW)
347 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
349 if (scoreboard_ui_enabled == 2)
351 entity prev_team = NULL;
352 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
354 if(tm.team == NUM_SPECTATOR)
356 if (tm == scoreboard_selected_team)
361 scoreboard_selected_team = prev_team;
365 entity prev_pl = NULL;
367 for(tm = teams.sort_next; tm; tm = tm.sort_next)
369 if(tm.team != NUM_SPECTATOR)
370 for(pl = players.sort_next; pl; pl = pl.sort_next)
372 if(pl.team != tm.team)
374 if (pl == scoreboard_selected_player)
380 scoreboard_selected_player = prev_pl;
384 else if(nPrimary == K_RIGHTARROW)
388 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
389 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
391 else if(nPrimary == K_LEFTARROW)
395 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
396 rankings_start_column = max(rankings_start_column - 1, 0);
398 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
402 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
404 if (scoreboard_ui_enabled == 2)
407 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
410 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
411 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
412 HUD_Scoreboard_UI_Disable();
414 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
417 HUD_Scoreboard_UI_Disable();
420 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
423 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
427 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
429 switch (scoreboard_selected_columns_layout)
432 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
434 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
435 scoreboard_selected_columns_layout = 1;
440 localcmd(sprintf("scoreboard_columns_set default\n"));
441 scoreboard_selected_columns_layout = 2;
444 localcmd(sprintf("scoreboard_columns_set all\n"));
445 scoreboard_selected_columns_layout = 0;
450 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
454 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
456 if (scoreboard_selected_player)
458 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
459 HUD_Scoreboard_UI_Disable();
463 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
467 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
469 if (scoreboard_selected_player)
470 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
473 else if(hit_con_bind || nPrimary == K_PAUSE)
479 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
480 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
482 #define SB_EXTRA_SORTING_FIELDS 5
483 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
484 void Scoreboard_InitScores()
488 ps_primary = ps_secondary = NULL;
489 ts_primary = ts_secondary = -1;
490 FOREACH(Scores, true, {
491 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
492 if(f == SFL_SORT_PRIO_PRIMARY)
494 if(f == SFL_SORT_PRIO_SECONDARY)
496 if(ps_primary == it || ps_secondary == it)
498 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
499 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
500 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
501 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
502 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
504 if(ps_secondary == NULL)
505 ps_secondary = ps_primary;
507 for(i = 0; i < MAX_TEAMSCORE; ++i)
509 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
510 if(f == SFL_SORT_PRIO_PRIMARY)
512 if(f == SFL_SORT_PRIO_SECONDARY)
515 if(ts_secondary == -1)
516 ts_secondary = ts_primary;
518 Cmd_Scoreboard_SetFields(0);
522 void Scoreboard_UpdatePlayerTeams()
524 static float update_time;
525 if (time <= update_time)
531 for(pl = players.sort_next; pl; pl = pl.sort_next)
534 int Team = entcs_GetScoreTeam(pl.sv_entnum);
535 if(SetTeam(pl, Team))
538 Scoreboard_UpdatePlayerPos(pl);
542 pl = players.sort_next;
547 print(strcat("PNUM: ", ftos(num), "\n"));
552 int Scoreboard_CompareScore(int vl, int vr, int f)
554 TC(int, vl); TC(int, vr); TC(int, f);
555 if(f & SFL_ZERO_IS_WORST)
557 if(vl == 0 && vr != 0)
559 if(vl != 0 && vr == 0)
563 return IS_INCREASING(f);
565 return IS_DECREASING(f);
569 float Scoreboard_ComparePlayerScores(entity left, entity right)
571 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
572 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
579 if(vl == NUM_SPECTATOR)
581 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
583 if(!left.gotscores && right.gotscores)
590 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
594 if (!fld) fld = ps_primary;
595 else if (ps_secondary == ps_primary) continue;
596 else fld = ps_secondary;
600 fld = sb_extra_sorting_field[i];
601 if (fld == ps_primary || fld == ps_secondary) continue;
605 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
606 if (r >= 0) return r;
609 if (left.sv_entnum < right.sv_entnum)
615 void Scoreboard_UpdatePlayerPos(entity player)
618 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
620 SORT_SWAP(player, ent);
622 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
624 SORT_SWAP(ent, player);
628 float Scoreboard_CompareTeamScores(entity left, entity right)
630 if(left.team == NUM_SPECTATOR)
632 if(right.team == NUM_SPECTATOR)
637 for(int i = -2; i < MAX_TEAMSCORE; ++i)
641 if (fld_idx == -1) fld_idx = ts_primary;
642 else if (ts_secondary == ts_primary) continue;
643 else fld_idx = ts_secondary;
648 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
651 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
652 if (r >= 0) return r;
655 if (left.team < right.team)
661 void Scoreboard_UpdateTeamPos(entity Team)
664 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
666 SORT_SWAP(Team, ent);
668 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
670 SORT_SWAP(ent, Team);
674 void Cmd_Scoreboard_Help()
676 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
677 LOG_HELP(_("Usage:"));
678 LOG_HELP("^2scoreboard_columns_set ^3default");
679 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
680 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
681 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
682 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
683 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
684 LOG_HELP(_("The following field names are recognized (case insensitive):"));
690 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
691 "of game types, then a slash, to make the field show up only in these\n"
692 "or in all but these game types. You can also specify 'all' as a\n"
693 "field to show all fields available for the current game mode."));
696 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
697 "include/exclude ALL teams/noteams game modes."));
700 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
701 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
702 "right of the vertical bar aligned to the right."));
703 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
704 "other gamemodes except DM."));
707 // NOTE: adding a gametype with ? to not warn for an optional field
708 // make sure it's excluded in a previous exclusive rule, if any
709 // otherwise the previous exclusive rule warns anyway
710 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
711 #define SCOREBOARD_DEFAULT_COLUMNS \
712 "ping pl fps name |" \
713 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
714 " -teams,lms/deaths +ft,tdm/deaths" \
716 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
717 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
718 " +tdm,ft,dom,ons,as/teamkills"\
719 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
720 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
721 " +lms/lives +lms/rank" \
722 " +kh/kckills +kh/losses +kh/caps" \
723 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
724 " +as/objectives +nb/faults +nb/goals" \
725 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
726 " +dom/ticks +dom/takes" \
727 " -lms,rc,cts,inv,nb/score"
729 void Cmd_Scoreboard_SetFields(int argc)
734 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
738 return; // do nothing, we don't know gametype and scores yet
740 // sbt_fields uses strunzone on the titles!
741 if(!sbt_field_title[0])
742 for(i = 0; i < MAX_SBT_FIELDS; ++i)
743 sbt_field_title[i] = strzone("(null)");
745 // TODO: re enable with gametype dependant cvars?
746 if(argc < 3) // no arguments provided
747 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
750 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
754 if(argv(2) == "default" || argv(2) == "expand_default")
756 if(argv(2) == "expand_default")
757 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
758 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
760 else if(argv(2) == "all" || argv(2) == "ALL")
762 string s = "ping pl name |"; // scores without label (not really scores)
765 // scores without label
766 s = strcat(s, " ", "sum");
767 s = strcat(s, " ", "kdratio");
768 s = strcat(s, " ", "frags");
770 FOREACH(Scores, true, {
772 if(it != ps_secondary)
773 if(scores_label(it) != "")
774 s = strcat(s, " ", scores_label(it));
776 if(ps_secondary != ps_primary)
777 s = strcat(s, " ", scores_label(ps_secondary));
778 s = strcat(s, " ", scores_label(ps_primary));
779 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
786 hud_fontsize = HUD_GetFontsize("hud_fontsize");
788 for(i = 1; i < argc - 1; ++i)
791 bool nocomplain = false;
792 if(substring(str, 0, 1) == "?")
795 str = substring(str, 1, strlen(str) - 1);
798 slash = strstrofs(str, "/", 0);
801 pattern = substring(str, 0, slash);
802 str = substring(str, slash + 1, strlen(str) - (slash + 1));
804 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
808 str = strtolower(str);
809 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
810 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
815 // fields without a label (not networked via the score system)
816 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
817 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
818 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
819 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
820 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
821 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
822 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
823 default: // fields with a label
825 // map alternative labels
826 if (str == "damage") str = "dmg";
827 if (str == "damagetaken") str = "dmgtaken";
829 FOREACH(Scores, true, {
830 if (str == strtolower(scores_label(it))) {
832 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
836 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
837 if(!nocomplain && str != "fps") // server can disable the fps field
838 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
840 strfree(sbt_field_title[sbt_num_fields]);
841 sbt_field_size[sbt_num_fields] = 0;
845 sbt_field[sbt_num_fields] = j;
848 if(j == ps_secondary)
849 have_secondary = true;
854 if(sbt_num_fields >= MAX_SBT_FIELDS)
858 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
860 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
861 have_secondary = true;
862 if(ps_primary == ps_secondary)
863 have_secondary = true;
864 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
866 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
870 strfree(sbt_field_title[sbt_num_fields]);
871 for(i = sbt_num_fields; i > 0; --i)
873 sbt_field_title[i] = sbt_field_title[i-1];
874 sbt_field_size[i] = sbt_field_size[i-1];
875 sbt_field[i] = sbt_field[i-1];
877 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
878 sbt_field[0] = SP_NAME;
880 LOG_INFO("fixed missing field 'name'");
884 strfree(sbt_field_title[sbt_num_fields]);
885 for(i = sbt_num_fields; i > 1; --i)
887 sbt_field_title[i] = sbt_field_title[i-1];
888 sbt_field_size[i] = sbt_field_size[i-1];
889 sbt_field[i] = sbt_field[i-1];
891 sbt_field_title[1] = strzone("|");
892 sbt_field[1] = SP_SEPARATOR;
893 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
895 LOG_INFO("fixed missing field '|'");
898 else if(!have_separator)
900 strcpy(sbt_field_title[sbt_num_fields], "|");
901 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
902 sbt_field[sbt_num_fields] = SP_SEPARATOR;
904 LOG_INFO("fixed missing field '|'");
908 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
909 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
910 sbt_field[sbt_num_fields] = ps_secondary;
912 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
916 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
917 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
918 sbt_field[sbt_num_fields] = ps_primary;
920 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
924 sbt_field[sbt_num_fields] = SP_END;
927 string Scoreboard_AddPlayerId(string pl_name, entity pl)
929 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
930 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
931 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
935 vector sbt_field_rgb;
936 string sbt_field_icon0;
937 string sbt_field_icon1;
938 string sbt_field_icon2;
939 vector sbt_field_icon0_rgb;
940 vector sbt_field_icon1_rgb;
941 vector sbt_field_icon2_rgb;
942 string Scoreboard_GetName(entity pl)
944 if(ready_waiting && pl.ready)
946 sbt_field_icon0 = "gfx/scoreboard/player_ready";
950 int f = entcs_GetClientColors(pl.sv_entnum);
952 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
953 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
954 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
955 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
956 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
959 return entcs_GetName(pl.sv_entnum);
962 string Scoreboard_GetField(entity pl, PlayerScoreField field)
964 float tmp, num, denom;
967 sbt_field_rgb = '1 1 1';
968 sbt_field_icon0 = "";
969 sbt_field_icon1 = "";
970 sbt_field_icon2 = "";
971 sbt_field_icon0_rgb = '1 1 1';
972 sbt_field_icon1_rgb = '1 1 1';
973 sbt_field_icon2_rgb = '1 1 1';
978 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
979 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
983 tmp = max(0, min(220, f-80)) / 220;
984 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
990 f = pl.ping_packetloss;
991 tmp = pl.ping_movementloss;
992 if(f == 0 && tmp == 0)
994 str = ftos(ceil(f * 100));
996 str = strcat(str, "~", ftos(ceil(tmp * 100)));
997 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
998 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1002 str = Scoreboard_GetName(pl);
1003 if (autocvar_hud_panel_scoreboard_playerid)
1004 str = Scoreboard_AddPlayerId(str, pl);
1008 f = pl.(scores(SP_KILLS));
1009 f -= pl.(scores(SP_SUICIDES));
1013 num = pl.(scores(SP_KILLS));
1014 denom = pl.(scores(SP_DEATHS));
1017 sbt_field_rgb = '0 1 0';
1018 str = sprintf("%d", num);
1019 } else if(num <= 0) {
1020 sbt_field_rgb = '1 0 0';
1021 str = sprintf("%.1f", num/denom);
1023 str = sprintf("%.1f", num/denom);
1027 f = pl.(scores(SP_KILLS));
1028 f -= pl.(scores(SP_DEATHS));
1031 sbt_field_rgb = '0 1 0';
1033 sbt_field_rgb = '1 1 1';
1035 sbt_field_rgb = '1 0 0';
1041 float elo = pl.(scores(SP_ELO));
1043 case -1: return "...";
1044 case -2: return _("N/A");
1045 default: return ftos(elo);
1051 float fps = pl.(scores(SP_FPS));
1054 sbt_field_rgb = '1 1 1';
1055 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1057 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1058 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1062 case SP_DMG: case SP_DMGTAKEN:
1063 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1065 default: case SP_SCORE:
1066 tmp = pl.(scores(field));
1067 f = scores_flags(field);
1068 if(field == ps_primary)
1069 sbt_field_rgb = '1 1 0';
1070 else if(field == ps_secondary)
1071 sbt_field_rgb = '0 1 1';
1073 sbt_field_rgb = '1 1 1';
1074 return ScoreString(f, tmp);
1079 float sbt_fixcolumnwidth_len;
1080 float sbt_fixcolumnwidth_iconlen;
1081 float sbt_fixcolumnwidth_marginlen;
1083 string Scoreboard_FixColumnWidth(int i, string str)
1089 sbt_fixcolumnwidth_iconlen = 0;
1091 if(sbt_field_icon0 != "")
1093 sz = draw_getimagesize(sbt_field_icon0);
1095 if(sbt_fixcolumnwidth_iconlen < f)
1096 sbt_fixcolumnwidth_iconlen = f;
1099 if(sbt_field_icon1 != "")
1101 sz = draw_getimagesize(sbt_field_icon1);
1103 if(sbt_fixcolumnwidth_iconlen < f)
1104 sbt_fixcolumnwidth_iconlen = f;
1107 if(sbt_field_icon2 != "")
1109 sz = draw_getimagesize(sbt_field_icon2);
1111 if(sbt_fixcolumnwidth_iconlen < f)
1112 sbt_fixcolumnwidth_iconlen = f;
1115 if(sbt_fixcolumnwidth_iconlen != 0)
1117 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1118 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1121 sbt_fixcolumnwidth_marginlen = 0;
1123 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1126 float remaining_space = 0;
1127 for(j = 0; j < sbt_num_fields; ++j)
1129 if (sbt_field[i] != SP_SEPARATOR)
1130 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1131 sbt_field_size[i] = panel_size.x - remaining_space;
1133 if (sbt_fixcolumnwidth_iconlen != 0)
1134 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1135 float namesize = panel_size.x - remaining_space;
1136 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1137 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1139 max_namesize = vid_conwidth - remaining_space;
1142 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1144 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1145 if(sbt_field_size[i] < f)
1146 sbt_field_size[i] = f;
1151 void Scoreboard_initFieldSizes()
1153 for(int i = 0; i < sbt_num_fields; ++i)
1155 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1156 Scoreboard_FixColumnWidth(i, "");
1160 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1163 vector column_dim = eY * panel_size.y;
1165 column_dim.y -= 1.25 * hud_fontsize.y;
1166 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1167 pos.x += hud_fontsize.x * 0.5;
1168 for(i = 0; i < sbt_num_fields; ++i)
1170 if(sbt_field[i] == SP_SEPARATOR)
1172 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1175 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1176 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1177 pos.x += column_dim.x;
1179 if(sbt_field[i] == SP_SEPARATOR)
1181 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1182 for(i = sbt_num_fields - 1; i > 0; --i)
1184 if(sbt_field[i] == SP_SEPARATOR)
1187 pos.x -= sbt_field_size[i];
1192 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);
1196 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1197 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1198 pos.x -= hud_fontsize.x;
1202 pos.x = panel_pos.x;
1203 pos.y += 1.25 * hud_fontsize.y;
1207 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1209 TC(bool, is_self); TC(int, pl_number);
1211 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1213 vector h_pos = item_pos;
1214 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1215 // alternated rows highlighting
1216 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1218 if (pl == scoreboard_selected_player)
1219 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1222 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1223 else if((sbt_highlight) && (!(pl_number % 2)))
1224 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1226 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1228 vector pos = item_pos;
1229 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1231 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1233 pos.x += hud_fontsize.x * 0.5;
1234 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1235 vector tmp = '0 0 0';
1237 PlayerScoreField field;
1238 for(i = 0; i < sbt_num_fields; ++i)
1240 field = sbt_field[i];
1241 if(field == SP_SEPARATOR)
1244 if(is_spec && field != SP_NAME && field != SP_PING) {
1245 pos.x += sbt_field_size[i] + hud_fontsize.x;
1248 str = Scoreboard_GetField(pl, field);
1249 str = Scoreboard_FixColumnWidth(i, str);
1251 pos.x += sbt_field_size[i] + hud_fontsize.x;
1253 if(field == SP_NAME) {
1254 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1255 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1257 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1258 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1261 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1262 if(sbt_field_icon0 != "")
1263 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1264 if(sbt_field_icon1 != "")
1265 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1266 if(sbt_field_icon2 != "")
1267 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1270 if(sbt_field[i] == SP_SEPARATOR)
1272 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1273 for(i = sbt_num_fields-1; i > 0; --i)
1275 field = sbt_field[i];
1276 if(field == SP_SEPARATOR)
1279 if(is_spec && field != SP_NAME && field != SP_PING) {
1280 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1284 str = Scoreboard_GetField(pl, field);
1285 str = Scoreboard_FixColumnWidth(i, str);
1287 if(field == SP_NAME) {
1288 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1289 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1291 tmp.x = sbt_fixcolumnwidth_len;
1292 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1295 tmp.x = sbt_field_size[i];
1296 if(sbt_field_icon0 != "")
1297 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1298 if(sbt_field_icon1 != "")
1299 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1300 if(sbt_field_icon2 != "")
1301 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1302 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1307 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1310 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1313 vector h_pos = item_pos;
1314 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1316 bool complete = (this_team == NUM_SPECTATOR);
1319 if((sbt_highlight) && (!(pl_number % 2)))
1320 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1322 vector pos = item_pos;
1323 pos.x += hud_fontsize.x * 0.5;
1324 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1326 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1328 width_limit -= stringwidth("...", false, hud_fontsize);
1329 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1330 static float max_name_width = 0;
1332 float fieldsize = 0;
1333 float min_fieldsize = 0;
1334 float fieldpadding = hud_fontsize.x * 0.25;
1335 if(this_team == NUM_SPECTATOR)
1337 if(autocvar_hud_panel_scoreboard_spectators_showping)
1338 min_fieldsize = stringwidth("999", false, hud_fontsize);
1340 else if(autocvar_hud_panel_scoreboard_others_showscore)
1341 min_fieldsize = stringwidth("99", false, hud_fontsize);
1342 for(i = 0; pl; pl = pl.sort_next)
1344 if(pl.team != this_team)
1346 if(pl == ignored_pl)
1350 if(this_team == NUM_SPECTATOR)
1352 if(autocvar_hud_panel_scoreboard_spectators_showping)
1353 field = Scoreboard_GetField(pl, SP_PING);
1355 else if(autocvar_hud_panel_scoreboard_others_showscore)
1356 field = Scoreboard_GetField(pl, SP_SCORE);
1358 string str = entcs_GetName(pl.sv_entnum);
1359 if (autocvar_hud_panel_scoreboard_playerid)
1360 str = Scoreboard_AddPlayerId(str, pl);
1361 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1362 float column_width = stringwidth(str, true, hud_fontsize);
1363 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1365 if(column_width > max_name_width)
1366 max_name_width = column_width;
1367 column_width = max_name_width;
1371 fieldsize = stringwidth(field, false, hud_fontsize);
1372 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1375 if(pos.x + column_width > width_limit)
1380 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1385 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1386 pos.y += hud_fontsize.y * 1.25;
1390 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1392 if (pl == scoreboard_selected_player)
1394 h_size.x = column_width + hud_fontsize.x * 0.25;
1395 h_size.y = hud_fontsize.y;
1396 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1400 vector name_pos = pos;
1401 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1402 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1403 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1406 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1407 h_size.y = hud_fontsize.y;
1408 vector field_pos = pos;
1409 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1410 field_pos.x += column_width - h_size.x;
1412 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1413 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1414 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1418 h_size.x = column_width + hud_fontsize.x * 0.25;
1419 h_size.y = hud_fontsize.y;
1420 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1422 pos.x += column_width;
1423 pos.x += hud_fontsize.x;
1425 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1428 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1430 int max_players = 999;
1431 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1433 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1436 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1437 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1438 height /= team_count;
1441 height -= panel_bg_padding * 2; // - padding
1442 max_players = floor(height / (hud_fontsize.y * 1.25));
1443 if(max_players <= 1)
1445 if(max_players == tm.team_size)
1450 entity me = playerslots[current_player];
1452 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1453 panel_size.y += panel_bg_padding * 2;
1455 vector scoreboard_selected_hl_pos = pos;
1456 vector scoreboard_selected_hl_size = '0 0 0';
1457 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1458 scoreboard_selected_hl_size.y = panel_size.y;
1462 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1463 if(panel.current_panel_bg != "0")
1464 end_pos.y += panel_bg_border * 2;
1466 if(panel_bg_padding)
1468 panel_pos += '1 1 0' * panel_bg_padding;
1469 panel_size -= '2 2 0' * panel_bg_padding;
1473 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1477 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1479 pos.y += 1.25 * hud_fontsize.y;
1482 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1484 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1487 // print header row and highlight columns
1488 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1490 // fill the table and draw the rows
1491 bool is_self = false;
1492 bool self_shown = false;
1494 for(pl = players.sort_next; pl; pl = pl.sort_next)
1496 if(pl.team != tm.team)
1498 if(i == max_players - 2 && pl != me)
1500 if(!self_shown && me.team == tm.team)
1502 Scoreboard_DrawItem(pos, rgb, me, true, i);
1504 pos.y += 1.25 * hud_fontsize.y;
1508 if(i >= max_players - 1)
1510 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1513 is_self = (pl.sv_entnum == current_player);
1514 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1517 pos.y += 1.25 * hud_fontsize.y;
1521 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1523 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1525 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1526 _alpha *= panel_fg_alpha;
1528 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1532 panel_size.x += panel_bg_padding * 2; // restore initial width
1536 bool Scoreboard_WouldDraw()
1538 if (scoreboard_ui_enabled)
1540 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1542 else if (QuickMenu_IsOpened())
1544 else if (HUD_Radar_Clickable())
1546 else if (scoreboard_showscores)
1548 else if (intermission == 1)
1550 else if (intermission == 2)
1552 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1553 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1557 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1562 float average_accuracy;
1563 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1565 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1567 WepSet weapons_stat = WepSet_GetFromStat();
1568 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1569 int disownedcnt = 0;
1571 FOREACH(Weapons, it != WEP_Null, {
1572 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1574 WepSet set = it.m_wepset;
1575 if(it.spawnflags & WEP_TYPE_OTHER)
1580 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1582 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1589 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1590 if (weapon_cnt <= 0) return pos;
1593 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1595 int columns = ceil(weapon_cnt / rows);
1597 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1598 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1599 float height = weapon_height + hud_fontsize.y;
1601 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);
1602 pos.y += 1.25 * hud_fontsize.y;
1603 if(panel.current_panel_bg != "0")
1604 pos.y += panel_bg_border;
1607 panel_size.y = height * rows;
1608 panel_size.y += panel_bg_padding * 2;
1610 float panel_bg_alpha_save = panel_bg_alpha;
1611 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1613 panel_bg_alpha = panel_bg_alpha_save;
1615 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1616 if(panel.current_panel_bg != "0")
1617 end_pos.y += panel_bg_border * 2;
1619 if(panel_bg_padding)
1621 panel_pos += '1 1 0' * panel_bg_padding;
1622 panel_size -= '2 2 0' * panel_bg_padding;
1626 vector tmp = panel_size;
1628 float weapon_width = tmp.x / columns / rows;
1631 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1635 // column highlighting
1636 for (int i = 0; i < columns; ++i)
1638 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);
1641 for (int i = 0; i < rows; ++i)
1642 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1645 average_accuracy = 0;
1646 int weapons_with_stats = 0;
1648 pos.x += weapon_width / 2;
1650 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1653 Accuracy_LoadColors();
1655 float oldposx = pos.x;
1659 FOREACH(Weapons, it != WEP_Null, {
1660 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1662 WepSet set = it.m_wepset;
1663 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1665 if (it.spawnflags & WEP_TYPE_OTHER)
1669 if (weapon_stats >= 0)
1670 weapon_alpha = sbt_fg_alpha;
1672 weapon_alpha = 0.2 * sbt_fg_alpha;
1675 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1677 if (weapon_stats >= 0) {
1678 weapons_with_stats += 1;
1679 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1681 string s = sprintf("%d%%", weapon_stats * 100);
1682 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1684 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1685 rgb = Accuracy_GetColor(weapon_stats);
1687 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1689 tmpos.x += weapon_width * rows;
1690 pos.x += weapon_width * rows;
1691 if (rows == 2 && column == columns - 1) {
1699 if (weapons_with_stats)
1700 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1702 panel_size.x += panel_bg_padding * 2; // restore initial width
1707 bool is_item_filtered(entity it)
1709 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1711 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1714 if (it.instanceOfArmor || it.instanceOfHealth)
1716 int ha_mask = floor(mask) % 10;
1719 default: return false;
1720 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1721 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1722 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1723 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1726 if (it.instanceOfAmmo)
1728 int ammo_mask = floor(mask / 10) % 10;
1729 return (ammo_mask == 1);
1734 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1736 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1738 int disowned_cnt = 0;
1739 int uninteresting_cnt = 0;
1740 IL_EACH(default_order_items, true, {
1741 int q = g_inventory.inv_items[it.m_id];
1742 //q = 1; // debug: display all items
1743 if (is_item_filtered(it))
1744 ++uninteresting_cnt;
1748 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1749 int n = items_cnt - disowned_cnt;
1750 if (n <= 0) return pos;
1752 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1753 int columns = max(6, ceil(n / rows));
1755 float item_height = hud_fontsize.y * 2.3;
1756 float height = item_height + hud_fontsize.y;
1758 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1759 pos.y += 1.25 * hud_fontsize.y;
1760 if(panel.current_panel_bg != "0")
1761 pos.y += panel_bg_border;
1764 panel_size.y = height * rows;
1765 panel_size.y += panel_bg_padding * 2;
1767 float panel_bg_alpha_save = panel_bg_alpha;
1768 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1770 panel_bg_alpha = panel_bg_alpha_save;
1772 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1773 if(panel.current_panel_bg != "0")
1774 end_pos.y += panel_bg_border * 2;
1776 if(panel_bg_padding)
1778 panel_pos += '1 1 0' * panel_bg_padding;
1779 panel_size -= '2 2 0' * panel_bg_padding;
1783 vector tmp = panel_size;
1785 float item_width = tmp.x / columns / rows;
1788 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1792 // column highlighting
1793 for (int i = 0; i < columns; ++i)
1795 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);
1798 for (int i = 0; i < rows; ++i)
1799 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1803 pos.x += item_width / 2;
1805 float oldposx = pos.x;
1809 IL_EACH(default_order_items, !is_item_filtered(it), {
1810 int n = g_inventory.inv_items[it.m_id];
1811 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1812 if (n <= 0) continue;
1813 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);
1815 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1816 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1817 tmpos.x += item_width * rows;
1818 pos.x += item_width * rows;
1819 if (rows == 2 && column == columns - 1) {
1827 panel_size.x += panel_bg_padding * 2; // restore initial width
1832 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1834 pos.x += hud_fontsize.x * 0.25;
1835 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1836 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1837 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1839 pos.y += hud_fontsize.y;
1844 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1845 float stat_secrets_found, stat_secrets_total;
1846 float stat_monsters_killed, stat_monsters_total;
1850 // get monster stats
1851 stat_monsters_killed = STAT(MONSTERS_KILLED);
1852 stat_monsters_total = STAT(MONSTERS_TOTAL);
1854 // get secrets stats
1855 stat_secrets_found = STAT(SECRETS_FOUND);
1856 stat_secrets_total = STAT(SECRETS_TOTAL);
1858 // get number of rows
1859 if(stat_secrets_total)
1861 if(stat_monsters_total)
1864 // if no rows, return
1868 // draw table header
1869 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1870 pos.y += 1.25 * hud_fontsize.y;
1871 if(panel.current_panel_bg != "0")
1872 pos.y += panel_bg_border;
1875 panel_size.y = hud_fontsize.y * rows;
1876 panel_size.y += panel_bg_padding * 2;
1879 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1880 if(panel.current_panel_bg != "0")
1881 end_pos.y += panel_bg_border * 2;
1883 if(panel_bg_padding)
1885 panel_pos += '1 1 0' * panel_bg_padding;
1886 panel_size -= '2 2 0' * panel_bg_padding;
1890 vector tmp = panel_size;
1893 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1896 if(stat_monsters_total)
1898 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1899 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1903 if(stat_secrets_total)
1905 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1906 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1909 panel_size.x += panel_bg_padding * 2; // restore initial width
1913 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1916 RANKINGS_RECEIVED_CNT = 0;
1917 for (i=RANKINGS_CNT-1; i>=0; --i)
1919 ++RANKINGS_RECEIVED_CNT;
1921 if (RANKINGS_RECEIVED_CNT == 0)
1924 vector hl_rgb = rgb + '0.5 0.5 0.5';
1926 vector scoreboard_selected_hl_pos = pos;
1928 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1929 pos.y += 1.25 * hud_fontsize.y;
1930 if(panel.current_panel_bg != "0")
1931 pos.y += panel_bg_border;
1933 vector scoreboard_selected_hl_size = '0 0 0';
1934 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1935 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1940 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1942 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1947 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1949 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1953 float ranksize = 3 * hud_fontsize.x;
1954 float timesize = 5 * hud_fontsize.x;
1955 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1956 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1957 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1960 rankings_cnt = RANKINGS_RECEIVED_CNT;
1961 rankings_rows = ceil(rankings_cnt / rankings_columns);
1964 // expand name column to fill the entire row
1965 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1966 namesize += available_space;
1967 columnsize.x += available_space;
1969 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1970 panel_size.y += panel_bg_padding * 2;
1971 scoreboard_selected_hl_size.y += panel_size.y;
1975 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1976 if(panel.current_panel_bg != "0")
1977 end_pos.y += panel_bg_border * 2;
1979 if(panel_bg_padding)
1981 panel_pos += '1 1 0' * panel_bg_padding;
1982 panel_size -= '2 2 0' * panel_bg_padding;
1988 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1990 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1992 int column = 0, j = 0;
1993 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1994 int start_item = rankings_start_column * rankings_rows;
1995 for(i = start_item; i < start_item + rankings_cnt; ++i)
1997 int t = grecordtime[i];
2001 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2002 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2003 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2004 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2006 str = count_ordinal(i+1);
2007 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2008 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2009 str = ColorTranslateRGB(grecordholder[i]);
2011 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2012 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2014 pos.y += 1.25 * hud_fontsize.y;
2016 if(j >= rankings_rows)
2020 pos.x += panel_size.x / rankings_columns;
2021 pos.y = panel_pos.y;
2024 strfree(zoned_name_self);
2026 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2028 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2029 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2032 panel_size.x += panel_bg_padding * 2; // restore initial width
2036 bool have_weapon_stats;
2037 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2039 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2041 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2044 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2045 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2051 if (!have_weapon_stats)
2053 FOREACH(Weapons, it != WEP_Null, {
2054 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2055 if (weapon_stats >= 0)
2057 have_weapon_stats = true;
2061 if (!have_weapon_stats)
2068 bool have_item_stats;
2069 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2071 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2073 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2076 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2077 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2083 if (!have_item_stats)
2085 IL_EACH(default_order_items, true, {
2086 if (!is_item_filtered(it))
2088 int q = g_inventory.inv_items[it.m_id];
2089 //q = 1; // debug: display all items
2092 have_item_stats = true;
2097 if (!have_item_stats)
2104 vector Scoreboard_Spectators_Draw(vector pos) {
2109 for(pl = players.sort_next; pl; pl = pl.sort_next)
2111 if(pl.team == NUM_SPECTATOR)
2113 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2114 if(tm.team == NUM_SPECTATOR)
2116 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2117 draw_beginBoldFont();
2118 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2120 pos.y += 1.25 * hud_fontsize.y;
2122 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2123 pos.y += 1.25 * hud_fontsize.y;
2128 if (str != "") // if there's at least one spectator
2129 pos.y += 0.5 * hud_fontsize.y;
2134 void Scoreboard_Draw()
2136 if(!autocvar__hud_configure)
2138 if(!hud_draw_maximized) return;
2140 // frametime checks allow to toggle the scoreboard even when the game is paused
2141 if(scoreboard_active) {
2142 if (scoreboard_fade_alpha == 0)
2143 scoreboard_time = time;
2144 if(hud_configure_menu_open == 1)
2145 scoreboard_fade_alpha = 1;
2146 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2147 if (scoreboard_fadeinspeed && frametime)
2148 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2150 scoreboard_fade_alpha = 1;
2151 if(hud_fontsize_str != autocvar_hud_fontsize)
2153 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2154 Scoreboard_initFieldSizes();
2155 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2159 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2160 if (scoreboard_fadeoutspeed && frametime)
2161 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2163 scoreboard_fade_alpha = 0;
2166 if (!scoreboard_fade_alpha)
2168 scoreboard_acc_fade_alpha = 0;
2169 scoreboard_itemstats_fade_alpha = 0;
2174 scoreboard_fade_alpha = 0;
2176 if (autocvar_hud_panel_scoreboard_dynamichud)
2179 HUD_Scale_Disable();
2181 if(scoreboard_fade_alpha <= 0)
2183 panel_fade_alpha *= scoreboard_fade_alpha;
2184 HUD_Panel_LoadCvars();
2186 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2187 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2188 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2189 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2190 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2191 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2192 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2194 // don't overlap with con_notify
2195 if(!autocvar__hud_configure)
2196 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2198 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2199 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2200 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2201 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2202 panel_pos.x = scoreboard_left;
2203 panel_size.x = fixed_scoreboard_width;
2205 Scoreboard_UpdatePlayerTeams();
2207 scoreboard_top = panel_pos.y;
2208 vector pos = panel_pos;
2213 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2215 // Begin of Game Info Section
2216 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2217 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2219 // Game Info: Game Type
2220 if (scoreboard_ui_enabled == 2)
2221 str = _("Team Selection");
2223 str = MapInfo_Type_ToText(gametype);
2224 draw_beginBoldFont();
2225 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);
2228 pos.y += sb_gameinfo_type_fontsize.y;
2229 // Game Info: Game Detail
2230 if (scoreboard_ui_enabled == 2)
2232 if (scoreboard_selected_team)
2233 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2235 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2236 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);
2238 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2239 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2240 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);
2244 float tl = STAT(TIMELIMIT);
2245 float fl = STAT(FRAGLIMIT);
2246 float ll = STAT(LEADLIMIT);
2247 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2250 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2251 if(!gametype.m_hidelimits)
2256 str = strcat(str, "^7 / "); // delimiter
2259 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2260 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2261 (teamscores_label(ts_primary) == "fastest") ? "" :
2262 TranslateScoresLabel(teamscores_label(ts_primary))));
2266 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2267 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2268 (scores_label(ps_primary) == "fastest") ? "" :
2269 TranslateScoresLabel(scores_label(ps_primary))));
2274 if(tl > 0 || fl > 0)
2277 if (ll_and_fl && fl > 0)
2278 str = strcat(str, "^7 & ");
2280 str = strcat(str, "^7 / ");
2285 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2286 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2287 (teamscores_label(ts_primary) == "fastest") ? "" :
2288 TranslateScoresLabel(teamscores_label(ts_primary))));
2292 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2293 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2294 (scores_label(ps_primary) == "fastest") ? "" :
2295 TranslateScoresLabel(scores_label(ps_primary))));
2299 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
2301 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2302 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2304 // End of Game Info Section
2306 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2307 if(panel.current_panel_bg != "0")
2308 pos.y += panel_bg_border;
2310 // Draw the scoreboard
2311 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2314 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2318 vector panel_bg_color_save = panel_bg_color;
2319 vector team_score_baseoffset;
2320 vector team_size_baseoffset;
2321 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2323 // put team score to the left of scoreboard (and team size to the right)
2324 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2325 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2326 if(panel.current_panel_bg != "0")
2328 team_score_baseoffset.x -= panel_bg_border;
2329 team_size_baseoffset.x += panel_bg_border;
2334 // put team score to the right of scoreboard (and team size to the left)
2335 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2336 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2337 if(panel.current_panel_bg != "0")
2339 team_score_baseoffset.x += panel_bg_border;
2340 team_size_baseoffset.x -= panel_bg_border;
2344 int team_size_total = 0;
2345 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2347 // calculate team size total (sum of all team sizes)
2348 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2349 if(tm.team != NUM_SPECTATOR)
2350 team_size_total += tm.team_size;
2353 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2355 if(tm.team == NUM_SPECTATOR)
2360 draw_beginBoldFont();
2361 vector rgb = Team_ColorRGB(tm.team);
2362 str = ftos(tm.(teamscores(ts_primary)));
2363 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2365 // team score on the left (default)
2366 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2370 // team score on the right
2371 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2373 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2375 // team size (if set to show on the side)
2376 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2378 // calculate the starting position for the whole team size info string
2379 str = sprintf("%d/%d", tm.team_size, team_size_total);
2380 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2382 // team size on the left
2383 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2387 // team size on the right
2388 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2390 str = sprintf("%d", tm.team_size);
2391 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2392 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2393 str = sprintf("/%d", team_size_total);
2394 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2398 // secondary score, e.g. keyhunt
2399 if(ts_primary != ts_secondary)
2401 str = ftos(tm.(teamscores(ts_secondary)));
2402 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2405 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2410 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2413 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2416 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2417 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2418 else if(panel_bg_color_team > 0)
2419 panel_bg_color = rgb * panel_bg_color_team;
2421 panel_bg_color = rgb;
2422 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2424 panel_bg_color = panel_bg_color_save;
2428 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2429 if(tm.team != NUM_SPECTATOR)
2432 // display it anyway
2433 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2436 // draw scoreboard spectators before accuracy and item stats
2437 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2438 pos = Scoreboard_Spectators_Draw(pos);
2441 // draw accuracy and item stats
2442 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2443 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2444 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2445 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2447 // draw scoreboard spectators after accuracy and item stats and before rankings
2448 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2449 pos = Scoreboard_Spectators_Draw(pos);
2452 if(MUTATOR_CALLHOOK(ShowRankings)) {
2453 string ranktitle = M_ARGV(0, string);
2454 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2455 if(race_speedaward_alltimebest)
2458 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2462 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2463 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2464 str = strcat(str, " / ");
2466 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2467 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2468 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2469 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2471 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2476 // draw scoreboard spectators after rankings
2477 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2478 pos = Scoreboard_Spectators_Draw(pos);
2481 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2483 // draw scoreboard spectators after mapstats
2484 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2485 pos = Scoreboard_Spectators_Draw(pos);
2489 // print information about respawn status
2490 float respawn_time = STAT(RESPAWN_TIME);
2494 if(respawn_time < 0)
2496 // a negative number means we are awaiting respawn, time value is still the same
2497 respawn_time *= -1; // remove mark now that we checked it
2499 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2500 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2502 str = sprintf(_("^1Respawning in ^3%s^1..."),
2503 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2504 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2506 count_seconds(ceil(respawn_time - time))
2510 else if(time < respawn_time)
2512 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2513 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2514 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2516 count_seconds(ceil(respawn_time - time))
2520 else if(time >= respawn_time)
2521 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2523 pos.y += 1.2 * hud_fontsize.y;
2524 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2527 pos.y += hud_fontsize.y;
2528 if (scoreboard_fade_alpha < 1)
2529 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2530 else if (pos.y != scoreboard_bottom)
2532 if (pos.y > scoreboard_bottom)
2533 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2535 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2540 if (scoreboard_fade_alpha == 1)
2542 if (scoreboard_bottom > 0.95 * vid_conheight)
2543 rankings_rows = max(1, rankings_rows - 1);
2544 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2545 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2547 rankings_cnt = rankings_rows * rankings_columns;