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)
276 if (hudShiftState & S_SHIFT)
279 goto downarrow_action;
282 if (hudShiftState & S_SHIFT)
284 --scoreboard_selected_panel;
285 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
286 --scoreboard_selected_panel;
287 if (scoreboard_selected_panel < SB_PANEL_FIRST)
288 scoreboard_selected_panel = SB_PANEL_MAX;
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_MAX)
296 scoreboard_selected_panel = SB_PANEL_FIRST;
299 scoreboard_selected_panel_time = time;
301 else if(nPrimary == K_DOWNARROW)
305 LABEL(downarrow_action);
306 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
308 if (scoreboard_ui_enabled == 2)
310 entity curr_team = NULL;
311 bool scoreboard_selected_team_found = false;
312 if (!scoreboard_selected_team)
313 scoreboard_selected_team_found = true;
315 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
317 if(tm.team == NUM_SPECTATOR)
320 if (scoreboard_selected_team_found)
322 if (scoreboard_selected_team == tm)
323 scoreboard_selected_team_found = true;
326 if (curr_team == scoreboard_selected_team) // loop reached the last team
328 scoreboard_selected_team = curr_team;
333 entity curr_pl = NULL;
334 bool scoreboard_selected_player_found = false;
335 if (!scoreboard_selected_player)
336 scoreboard_selected_player_found = true;
338 for(tm = teams.sort_next; tm; tm = tm.sort_next)
340 if(tm.team != NUM_SPECTATOR)
341 for(pl = players.sort_next; pl; pl = pl.sort_next)
343 if(pl.team != tm.team)
346 if (scoreboard_selected_player_found)
348 if (scoreboard_selected_player == pl)
349 scoreboard_selected_player_found = true;
353 if (curr_pl == scoreboard_selected_player) // loop reached the last player
355 scoreboard_selected_player = curr_pl;
359 else if(nPrimary == K_UPARROW)
363 LABEL(uparrow_action);
364 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
366 if (scoreboard_ui_enabled == 2)
368 entity prev_team = NULL;
369 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
371 if(tm.team == NUM_SPECTATOR)
373 if (tm == scoreboard_selected_team)
378 scoreboard_selected_team = prev_team;
382 entity prev_pl = NULL;
384 for(tm = teams.sort_next; tm; tm = tm.sort_next)
386 if(tm.team != NUM_SPECTATOR)
387 for(pl = players.sort_next; pl; pl = pl.sort_next)
389 if(pl.team != tm.team)
391 if (pl == scoreboard_selected_player)
397 scoreboard_selected_player = prev_pl;
401 else if(nPrimary == K_RIGHTARROW)
405 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
406 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
408 else if(nPrimary == K_LEFTARROW)
412 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
413 rankings_start_column = max(rankings_start_column - 1, 0);
415 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
419 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
421 if (scoreboard_ui_enabled == 2)
424 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
427 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
428 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
429 HUD_Scoreboard_UI_Disable();
431 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
434 HUD_Scoreboard_UI_Disable();
437 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
440 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
444 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
446 switch (scoreboard_selected_columns_layout)
449 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
451 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
452 scoreboard_selected_columns_layout = 1;
457 localcmd(sprintf("scoreboard_columns_set default\n"));
458 scoreboard_selected_columns_layout = 2;
461 localcmd(sprintf("scoreboard_columns_set all\n"));
462 scoreboard_selected_columns_layout = 0;
467 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
471 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
473 if (scoreboard_selected_player)
475 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
476 HUD_Scoreboard_UI_Disable();
480 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
484 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
486 if (scoreboard_selected_player)
487 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
490 else if(hit_con_bind || nPrimary == K_PAUSE)
496 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
497 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
499 #define SB_EXTRA_SORTING_FIELDS 5
500 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
501 void Scoreboard_InitScores()
505 ps_primary = ps_secondary = NULL;
506 ts_primary = ts_secondary = -1;
507 FOREACH(Scores, true, {
508 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
509 if(f == SFL_SORT_PRIO_PRIMARY)
511 if(f == SFL_SORT_PRIO_SECONDARY)
513 if(ps_primary == it || ps_secondary == it)
515 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
516 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
517 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
518 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
519 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
521 if(ps_secondary == NULL)
522 ps_secondary = ps_primary;
524 for(i = 0; i < MAX_TEAMSCORE; ++i)
526 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
527 if(f == SFL_SORT_PRIO_PRIMARY)
529 if(f == SFL_SORT_PRIO_SECONDARY)
532 if(ts_secondary == -1)
533 ts_secondary = ts_primary;
535 Cmd_Scoreboard_SetFields(0);
539 void Scoreboard_UpdatePlayerTeams()
541 static float update_time;
542 if (time <= update_time)
548 for(pl = players.sort_next; pl; pl = pl.sort_next)
551 int Team = entcs_GetScoreTeam(pl.sv_entnum);
552 if(SetTeam(pl, Team))
555 Scoreboard_UpdatePlayerPos(pl);
559 pl = players.sort_next;
564 print(strcat("PNUM: ", ftos(num), "\n"));
569 int Scoreboard_CompareScore(int vl, int vr, int f)
571 TC(int, vl); TC(int, vr); TC(int, f);
572 if(f & SFL_ZERO_IS_WORST)
574 if(vl == 0 && vr != 0)
576 if(vl != 0 && vr == 0)
580 return IS_INCREASING(f);
582 return IS_DECREASING(f);
586 float Scoreboard_ComparePlayerScores(entity left, entity right)
588 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
589 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
596 if(vl == NUM_SPECTATOR)
598 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
600 if(!left.gotscores && right.gotscores)
607 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
611 if (!fld) fld = ps_primary;
612 else if (ps_secondary == ps_primary) continue;
613 else fld = ps_secondary;
617 fld = sb_extra_sorting_field[i];
618 if (fld == ps_primary || fld == ps_secondary) continue;
622 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
623 if (r >= 0) return r;
626 if (left.sv_entnum < right.sv_entnum)
632 void Scoreboard_UpdatePlayerPos(entity player)
635 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
637 SORT_SWAP(player, ent);
639 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
641 SORT_SWAP(ent, player);
645 float Scoreboard_CompareTeamScores(entity left, entity right)
647 if(left.team == NUM_SPECTATOR)
649 if(right.team == NUM_SPECTATOR)
654 for(int i = -2; i < MAX_TEAMSCORE; ++i)
658 if (fld_idx == -1) fld_idx = ts_primary;
659 else if (ts_secondary == ts_primary) continue;
660 else fld_idx = ts_secondary;
665 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
668 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
669 if (r >= 0) return r;
672 if (left.team < right.team)
678 void Scoreboard_UpdateTeamPos(entity Team)
681 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
683 SORT_SWAP(Team, ent);
685 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
687 SORT_SWAP(ent, Team);
691 void Cmd_Scoreboard_Help()
693 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
694 LOG_HELP(_("Usage:"));
695 LOG_HELP("^2scoreboard_columns_set ^3default");
696 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
697 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
698 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
699 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
700 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
701 LOG_HELP(_("The following field names are recognized (case insensitive):"));
707 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
708 "of game types, then a slash, to make the field show up only in these\n"
709 "or in all but these game types. You can also specify 'all' as a\n"
710 "field to show all fields available for the current game mode."));
713 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
714 "include/exclude ALL teams/noteams game modes."));
717 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
718 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
719 "right of the vertical bar aligned to the right."));
720 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
721 "other gamemodes except DM."));
724 // NOTE: adding a gametype with ? to not warn for an optional field
725 // make sure it's excluded in a previous exclusive rule, if any
726 // otherwise the previous exclusive rule warns anyway
727 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
728 #define SCOREBOARD_DEFAULT_COLUMNS \
729 "ping pl fps name |" \
730 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
731 " -teams,lms/deaths +ft,tdm/deaths" \
733 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
734 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
735 " +tdm,ft,dom,ons,as/teamkills"\
736 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
737 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
738 " +lms/lives +lms/rank" \
739 " +kh/kckills +kh/losses +kh/caps" \
740 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
741 " +as/objectives +nb/faults +nb/goals" \
742 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
743 " +dom/ticks +dom/takes" \
744 " -lms,rc,cts,inv,nb/score"
746 void Cmd_Scoreboard_SetFields(int argc)
751 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
755 return; // do nothing, we don't know gametype and scores yet
757 // sbt_fields uses strunzone on the titles!
758 if(!sbt_field_title[0])
759 for(i = 0; i < MAX_SBT_FIELDS; ++i)
760 sbt_field_title[i] = strzone("(null)");
762 // TODO: re enable with gametype dependant cvars?
763 if(argc < 3) // no arguments provided
764 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
767 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
771 if(argv(2) == "default" || argv(2) == "expand_default")
773 if(argv(2) == "expand_default")
774 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
775 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
777 else if(argv(2) == "all" || argv(2) == "ALL")
779 string s = "ping pl name |"; // scores without label (not really scores)
782 // scores without label
783 s = strcat(s, " ", "sum");
784 s = strcat(s, " ", "kdratio");
785 s = strcat(s, " ", "frags");
787 FOREACH(Scores, true, {
789 if(it != ps_secondary)
790 if(scores_label(it) != "")
791 s = strcat(s, " ", scores_label(it));
793 if(ps_secondary != ps_primary)
794 s = strcat(s, " ", scores_label(ps_secondary));
795 s = strcat(s, " ", scores_label(ps_primary));
796 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
803 hud_fontsize = HUD_GetFontsize("hud_fontsize");
805 for(i = 1; i < argc - 1; ++i)
808 bool nocomplain = false;
809 if(substring(str, 0, 1) == "?")
812 str = substring(str, 1, strlen(str) - 1);
815 slash = strstrofs(str, "/", 0);
818 pattern = substring(str, 0, slash);
819 str = substring(str, slash + 1, strlen(str) - (slash + 1));
821 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
825 str = strtolower(str);
826 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
827 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
832 // fields without a label (not networked via the score system)
833 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
834 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
835 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
836 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
837 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
838 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
839 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
840 default: // fields with a label
842 // map alternative labels
843 if (str == "damage") str = "dmg";
844 if (str == "damagetaken") str = "dmgtaken";
846 FOREACH(Scores, true, {
847 if (str == strtolower(scores_label(it))) {
849 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
853 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
854 if(!nocomplain && str != "fps") // server can disable the fps field
855 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
857 strfree(sbt_field_title[sbt_num_fields]);
858 sbt_field_size[sbt_num_fields] = 0;
862 sbt_field[sbt_num_fields] = j;
865 if(j == ps_secondary)
866 have_secondary = true;
871 if(sbt_num_fields >= MAX_SBT_FIELDS)
875 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
877 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
878 have_secondary = true;
879 if(ps_primary == ps_secondary)
880 have_secondary = true;
881 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
883 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
887 strfree(sbt_field_title[sbt_num_fields]);
888 for(i = sbt_num_fields; i > 0; --i)
890 sbt_field_title[i] = sbt_field_title[i-1];
891 sbt_field_size[i] = sbt_field_size[i-1];
892 sbt_field[i] = sbt_field[i-1];
894 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
895 sbt_field[0] = SP_NAME;
897 LOG_INFO("fixed missing field 'name'");
901 strfree(sbt_field_title[sbt_num_fields]);
902 for(i = sbt_num_fields; i > 1; --i)
904 sbt_field_title[i] = sbt_field_title[i-1];
905 sbt_field_size[i] = sbt_field_size[i-1];
906 sbt_field[i] = sbt_field[i-1];
908 sbt_field_title[1] = strzone("|");
909 sbt_field[1] = SP_SEPARATOR;
910 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
912 LOG_INFO("fixed missing field '|'");
915 else if(!have_separator)
917 strcpy(sbt_field_title[sbt_num_fields], "|");
918 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
919 sbt_field[sbt_num_fields] = SP_SEPARATOR;
921 LOG_INFO("fixed missing field '|'");
925 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
926 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
927 sbt_field[sbt_num_fields] = ps_secondary;
929 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
933 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
934 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
935 sbt_field[sbt_num_fields] = ps_primary;
937 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
941 sbt_field[sbt_num_fields] = SP_END;
944 string Scoreboard_AddPlayerId(string pl_name, entity pl)
946 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
947 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
948 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
952 vector sbt_field_rgb;
953 string sbt_field_icon0;
954 string sbt_field_icon1;
955 string sbt_field_icon2;
956 vector sbt_field_icon0_rgb;
957 vector sbt_field_icon1_rgb;
958 vector sbt_field_icon2_rgb;
959 string Scoreboard_GetName(entity pl)
961 if(ready_waiting && pl.ready)
963 sbt_field_icon0 = "gfx/scoreboard/player_ready";
967 int f = entcs_GetClientColors(pl.sv_entnum);
969 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
970 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
971 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
972 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
973 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
976 return entcs_GetName(pl.sv_entnum);
979 string Scoreboard_GetField(entity pl, PlayerScoreField field)
981 float tmp, num, denom;
984 sbt_field_rgb = '1 1 1';
985 sbt_field_icon0 = "";
986 sbt_field_icon1 = "";
987 sbt_field_icon2 = "";
988 sbt_field_icon0_rgb = '1 1 1';
989 sbt_field_icon1_rgb = '1 1 1';
990 sbt_field_icon2_rgb = '1 1 1';
995 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
996 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1000 tmp = max(0, min(220, f-80)) / 220;
1001 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1007 f = pl.ping_packetloss;
1008 tmp = pl.ping_movementloss;
1009 if(f == 0 && tmp == 0)
1011 str = ftos(ceil(f * 100));
1013 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1014 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1015 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1019 str = Scoreboard_GetName(pl);
1020 if (autocvar_hud_panel_scoreboard_playerid)
1021 str = Scoreboard_AddPlayerId(str, pl);
1025 f = pl.(scores(SP_KILLS));
1026 f -= pl.(scores(SP_SUICIDES));
1030 num = pl.(scores(SP_KILLS));
1031 denom = pl.(scores(SP_DEATHS));
1034 sbt_field_rgb = '0 1 0';
1035 str = sprintf("%d", num);
1036 } else if(num <= 0) {
1037 sbt_field_rgb = '1 0 0';
1038 str = sprintf("%.1f", num/denom);
1040 str = sprintf("%.1f", num/denom);
1044 f = pl.(scores(SP_KILLS));
1045 f -= pl.(scores(SP_DEATHS));
1048 sbt_field_rgb = '0 1 0';
1050 sbt_field_rgb = '1 1 1';
1052 sbt_field_rgb = '1 0 0';
1058 float elo = pl.(scores(SP_ELO));
1060 case -1: return "...";
1061 case -2: return _("N/A");
1062 default: return ftos(elo);
1068 float fps = pl.(scores(SP_FPS));
1071 sbt_field_rgb = '1 1 1';
1072 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1074 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1075 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1079 case SP_DMG: case SP_DMGTAKEN:
1080 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1082 default: case SP_SCORE:
1083 tmp = pl.(scores(field));
1084 f = scores_flags(field);
1085 if(field == ps_primary)
1086 sbt_field_rgb = '1 1 0';
1087 else if(field == ps_secondary)
1088 sbt_field_rgb = '0 1 1';
1090 sbt_field_rgb = '1 1 1';
1091 return ScoreString(f, tmp);
1096 float sbt_fixcolumnwidth_len;
1097 float sbt_fixcolumnwidth_iconlen;
1098 float sbt_fixcolumnwidth_marginlen;
1100 string Scoreboard_FixColumnWidth(int i, string str)
1106 sbt_fixcolumnwidth_iconlen = 0;
1108 if(sbt_field_icon0 != "")
1110 sz = draw_getimagesize(sbt_field_icon0);
1112 if(sbt_fixcolumnwidth_iconlen < f)
1113 sbt_fixcolumnwidth_iconlen = f;
1116 if(sbt_field_icon1 != "")
1118 sz = draw_getimagesize(sbt_field_icon1);
1120 if(sbt_fixcolumnwidth_iconlen < f)
1121 sbt_fixcolumnwidth_iconlen = f;
1124 if(sbt_field_icon2 != "")
1126 sz = draw_getimagesize(sbt_field_icon2);
1128 if(sbt_fixcolumnwidth_iconlen < f)
1129 sbt_fixcolumnwidth_iconlen = f;
1132 if(sbt_fixcolumnwidth_iconlen != 0)
1134 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1135 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1138 sbt_fixcolumnwidth_marginlen = 0;
1140 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1143 float remaining_space = 0;
1144 for(j = 0; j < sbt_num_fields; ++j)
1146 if (sbt_field[i] != SP_SEPARATOR)
1147 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1148 sbt_field_size[i] = panel_size.x - remaining_space;
1150 if (sbt_fixcolumnwidth_iconlen != 0)
1151 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1152 float namesize = panel_size.x - remaining_space;
1153 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1154 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1156 max_namesize = vid_conwidth - remaining_space;
1159 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1161 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1162 if(sbt_field_size[i] < f)
1163 sbt_field_size[i] = f;
1168 void Scoreboard_initFieldSizes()
1170 for(int i = 0; i < sbt_num_fields; ++i)
1172 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1173 Scoreboard_FixColumnWidth(i, "");
1177 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1180 vector column_dim = eY * panel_size.y;
1182 column_dim.y -= 1.25 * hud_fontsize.y;
1183 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1184 pos.x += hud_fontsize.x * 0.5;
1185 for(i = 0; i < sbt_num_fields; ++i)
1187 if(sbt_field[i] == SP_SEPARATOR)
1189 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1192 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1193 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1194 pos.x += column_dim.x;
1196 if(sbt_field[i] == SP_SEPARATOR)
1198 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1199 for(i = sbt_num_fields - 1; i > 0; --i)
1201 if(sbt_field[i] == SP_SEPARATOR)
1204 pos.x -= sbt_field_size[i];
1209 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1210 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1213 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1214 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1215 pos.x -= hud_fontsize.x;
1219 pos.x = panel_pos.x;
1220 pos.y += 1.25 * hud_fontsize.y;
1224 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1226 TC(bool, is_self); TC(int, pl_number);
1228 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1230 vector h_pos = item_pos;
1231 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1232 // alternated rows highlighting
1233 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1235 if (pl == scoreboard_selected_player)
1236 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1239 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1240 else if((sbt_highlight) && (!(pl_number % 2)))
1241 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1243 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1245 vector pos = item_pos;
1246 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1248 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1250 pos.x += hud_fontsize.x * 0.5;
1251 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1252 vector tmp = '0 0 0';
1254 PlayerScoreField field;
1255 for(i = 0; i < sbt_num_fields; ++i)
1257 field = sbt_field[i];
1258 if(field == SP_SEPARATOR)
1261 if(is_spec && field != SP_NAME && field != SP_PING) {
1262 pos.x += sbt_field_size[i] + hud_fontsize.x;
1265 str = Scoreboard_GetField(pl, field);
1266 str = Scoreboard_FixColumnWidth(i, str);
1268 pos.x += sbt_field_size[i] + hud_fontsize.x;
1270 if(field == SP_NAME) {
1271 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1272 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1274 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1275 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1278 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1279 if(sbt_field_icon0 != "")
1280 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1281 if(sbt_field_icon1 != "")
1282 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1283 if(sbt_field_icon2 != "")
1284 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1287 if(sbt_field[i] == SP_SEPARATOR)
1289 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1290 for(i = sbt_num_fields-1; i > 0; --i)
1292 field = sbt_field[i];
1293 if(field == SP_SEPARATOR)
1296 if(is_spec && field != SP_NAME && field != SP_PING) {
1297 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1301 str = Scoreboard_GetField(pl, field);
1302 str = Scoreboard_FixColumnWidth(i, str);
1304 if(field == SP_NAME) {
1305 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1306 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1308 tmp.x = sbt_fixcolumnwidth_len;
1309 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1312 tmp.x = sbt_field_size[i];
1313 if(sbt_field_icon0 != "")
1314 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1315 if(sbt_field_icon1 != "")
1316 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1317 if(sbt_field_icon2 != "")
1318 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1319 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1324 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1327 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1330 vector h_pos = item_pos;
1331 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1333 bool complete = (this_team == NUM_SPECTATOR);
1336 if((sbt_highlight) && (!(pl_number % 2)))
1337 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1339 vector pos = item_pos;
1340 pos.x += hud_fontsize.x * 0.5;
1341 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1343 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1345 width_limit -= stringwidth("...", false, hud_fontsize);
1346 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1347 static float max_name_width = 0;
1349 float fieldsize = 0;
1350 float min_fieldsize = 0;
1351 float fieldpadding = hud_fontsize.x * 0.25;
1352 if(this_team == NUM_SPECTATOR)
1354 if(autocvar_hud_panel_scoreboard_spectators_showping)
1355 min_fieldsize = stringwidth("999", false, hud_fontsize);
1357 else if(autocvar_hud_panel_scoreboard_others_showscore)
1358 min_fieldsize = stringwidth("99", false, hud_fontsize);
1359 for(i = 0; pl; pl = pl.sort_next)
1361 if(pl.team != this_team)
1363 if(pl == ignored_pl)
1367 if(this_team == NUM_SPECTATOR)
1369 if(autocvar_hud_panel_scoreboard_spectators_showping)
1370 field = Scoreboard_GetField(pl, SP_PING);
1372 else if(autocvar_hud_panel_scoreboard_others_showscore)
1373 field = Scoreboard_GetField(pl, SP_SCORE);
1375 string str = entcs_GetName(pl.sv_entnum);
1376 if (autocvar_hud_panel_scoreboard_playerid)
1377 str = Scoreboard_AddPlayerId(str, pl);
1378 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1379 float column_width = stringwidth(str, true, hud_fontsize);
1380 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1382 if(column_width > max_name_width)
1383 max_name_width = column_width;
1384 column_width = max_name_width;
1388 fieldsize = stringwidth(field, false, hud_fontsize);
1389 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1392 if(pos.x + column_width > width_limit)
1397 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1402 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1403 pos.y += hud_fontsize.y * 1.25;
1407 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1409 if (pl == scoreboard_selected_player)
1411 h_size.x = column_width + hud_fontsize.x * 0.25;
1412 h_size.y = hud_fontsize.y;
1413 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1417 vector name_pos = pos;
1418 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1419 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1420 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1423 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1424 h_size.y = hud_fontsize.y;
1425 vector field_pos = pos;
1426 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1427 field_pos.x += column_width - h_size.x;
1429 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1430 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1431 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1435 h_size.x = column_width + hud_fontsize.x * 0.25;
1436 h_size.y = hud_fontsize.y;
1437 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1439 pos.x += column_width;
1440 pos.x += hud_fontsize.x;
1442 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1445 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1447 int max_players = 999;
1448 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1450 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1453 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1454 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1455 height /= team_count;
1458 height -= panel_bg_padding * 2; // - padding
1459 max_players = floor(height / (hud_fontsize.y * 1.25));
1460 if(max_players <= 1)
1462 if(max_players == tm.team_size)
1467 entity me = playerslots[current_player];
1469 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1470 panel_size.y += panel_bg_padding * 2;
1472 vector scoreboard_selected_hl_pos = pos;
1473 vector scoreboard_selected_hl_size = '0 0 0';
1474 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1475 scoreboard_selected_hl_size.y = panel_size.y;
1479 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1480 if(panel.current_panel_bg != "0")
1481 end_pos.y += panel_bg_border * 2;
1483 if(panel_bg_padding)
1485 panel_pos += '1 1 0' * panel_bg_padding;
1486 panel_size -= '2 2 0' * panel_bg_padding;
1490 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1494 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1496 pos.y += 1.25 * hud_fontsize.y;
1499 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1501 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1504 // print header row and highlight columns
1505 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1507 // fill the table and draw the rows
1508 bool is_self = false;
1509 bool self_shown = false;
1511 for(pl = players.sort_next; pl; pl = pl.sort_next)
1513 if(pl.team != tm.team)
1515 if(i == max_players - 2 && pl != me)
1517 if(!self_shown && me.team == tm.team)
1519 Scoreboard_DrawItem(pos, rgb, me, true, i);
1521 pos.y += 1.25 * hud_fontsize.y;
1525 if(i >= max_players - 1)
1527 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1530 is_self = (pl.sv_entnum == current_player);
1531 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1534 pos.y += 1.25 * hud_fontsize.y;
1538 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1540 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1542 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1543 _alpha *= panel_fg_alpha;
1545 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1549 panel_size.x += panel_bg_padding * 2; // restore initial width
1553 bool Scoreboard_WouldDraw()
1555 if (scoreboard_ui_enabled)
1557 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1559 else if (QuickMenu_IsOpened())
1561 else if (HUD_Radar_Clickable())
1563 else if (scoreboard_showscores)
1565 else if (intermission == 1)
1567 else if (intermission == 2)
1569 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1570 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1574 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1579 float average_accuracy;
1580 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1582 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1584 WepSet weapons_stat = WepSet_GetFromStat();
1585 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1586 int disownedcnt = 0;
1588 FOREACH(Weapons, it != WEP_Null, {
1589 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1591 WepSet set = it.m_wepset;
1592 if(it.spawnflags & WEP_TYPE_OTHER)
1597 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1599 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1606 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1607 if (weapon_cnt <= 0) return pos;
1610 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1612 int columns = ceil(weapon_cnt / rows);
1614 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1615 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1616 float height = weapon_height + hud_fontsize.y;
1618 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);
1619 pos.y += 1.25 * hud_fontsize.y;
1620 if(panel.current_panel_bg != "0")
1621 pos.y += panel_bg_border;
1624 panel_size.y = height * rows;
1625 panel_size.y += panel_bg_padding * 2;
1627 float panel_bg_alpha_save = panel_bg_alpha;
1628 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1630 panel_bg_alpha = panel_bg_alpha_save;
1632 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1633 if(panel.current_panel_bg != "0")
1634 end_pos.y += panel_bg_border * 2;
1636 if(panel_bg_padding)
1638 panel_pos += '1 1 0' * panel_bg_padding;
1639 panel_size -= '2 2 0' * panel_bg_padding;
1643 vector tmp = panel_size;
1645 float weapon_width = tmp.x / columns / rows;
1648 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1652 // column highlighting
1653 for (int i = 0; i < columns; ++i)
1655 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);
1658 for (int i = 0; i < rows; ++i)
1659 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1662 average_accuracy = 0;
1663 int weapons_with_stats = 0;
1665 pos.x += weapon_width / 2;
1667 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1670 Accuracy_LoadColors();
1672 float oldposx = pos.x;
1676 FOREACH(Weapons, it != WEP_Null, {
1677 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1679 WepSet set = it.m_wepset;
1680 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1682 if (it.spawnflags & WEP_TYPE_OTHER)
1686 if (weapon_stats >= 0)
1687 weapon_alpha = sbt_fg_alpha;
1689 weapon_alpha = 0.2 * sbt_fg_alpha;
1692 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1694 if (weapon_stats >= 0) {
1695 weapons_with_stats += 1;
1696 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1698 string s = sprintf("%d%%", weapon_stats * 100);
1699 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1701 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1702 rgb = Accuracy_GetColor(weapon_stats);
1704 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1706 tmpos.x += weapon_width * rows;
1707 pos.x += weapon_width * rows;
1708 if (rows == 2 && column == columns - 1) {
1716 if (weapons_with_stats)
1717 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1719 panel_size.x += panel_bg_padding * 2; // restore initial width
1724 bool is_item_filtered(entity it)
1726 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1728 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1731 if (it.instanceOfArmor || it.instanceOfHealth)
1733 int ha_mask = floor(mask) % 10;
1736 default: return false;
1737 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1738 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1739 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1740 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1743 if (it.instanceOfAmmo)
1745 int ammo_mask = floor(mask / 10) % 10;
1746 return (ammo_mask == 1);
1751 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1753 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1755 int disowned_cnt = 0;
1756 int uninteresting_cnt = 0;
1757 IL_EACH(default_order_items, true, {
1758 int q = g_inventory.inv_items[it.m_id];
1759 //q = 1; // debug: display all items
1760 if (is_item_filtered(it))
1761 ++uninteresting_cnt;
1765 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1766 int n = items_cnt - disowned_cnt;
1767 if (n <= 0) return pos;
1769 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1770 int columns = max(6, ceil(n / rows));
1772 float item_height = hud_fontsize.y * 2.3;
1773 float height = item_height + hud_fontsize.y;
1775 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1776 pos.y += 1.25 * hud_fontsize.y;
1777 if(panel.current_panel_bg != "0")
1778 pos.y += panel_bg_border;
1781 panel_size.y = height * rows;
1782 panel_size.y += panel_bg_padding * 2;
1784 float panel_bg_alpha_save = panel_bg_alpha;
1785 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1787 panel_bg_alpha = panel_bg_alpha_save;
1789 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1790 if(panel.current_panel_bg != "0")
1791 end_pos.y += panel_bg_border * 2;
1793 if(panel_bg_padding)
1795 panel_pos += '1 1 0' * panel_bg_padding;
1796 panel_size -= '2 2 0' * panel_bg_padding;
1800 vector tmp = panel_size;
1802 float item_width = tmp.x / columns / rows;
1805 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1809 // column highlighting
1810 for (int i = 0; i < columns; ++i)
1812 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);
1815 for (int i = 0; i < rows; ++i)
1816 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1820 pos.x += item_width / 2;
1822 float oldposx = pos.x;
1826 IL_EACH(default_order_items, !is_item_filtered(it), {
1827 int n = g_inventory.inv_items[it.m_id];
1828 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1829 if (n <= 0) continue;
1830 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);
1832 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1833 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1834 tmpos.x += item_width * rows;
1835 pos.x += item_width * rows;
1836 if (rows == 2 && column == columns - 1) {
1844 panel_size.x += panel_bg_padding * 2; // restore initial width
1849 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1851 pos.x += hud_fontsize.x * 0.25;
1852 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1853 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1854 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1856 pos.y += hud_fontsize.y;
1861 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1862 float stat_secrets_found, stat_secrets_total;
1863 float stat_monsters_killed, stat_monsters_total;
1867 // get monster stats
1868 stat_monsters_killed = STAT(MONSTERS_KILLED);
1869 stat_monsters_total = STAT(MONSTERS_TOTAL);
1871 // get secrets stats
1872 stat_secrets_found = STAT(SECRETS_FOUND);
1873 stat_secrets_total = STAT(SECRETS_TOTAL);
1875 // get number of rows
1876 if(stat_secrets_total)
1878 if(stat_monsters_total)
1881 // if no rows, return
1885 // draw table header
1886 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1887 pos.y += 1.25 * hud_fontsize.y;
1888 if(panel.current_panel_bg != "0")
1889 pos.y += panel_bg_border;
1892 panel_size.y = hud_fontsize.y * rows;
1893 panel_size.y += panel_bg_padding * 2;
1896 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1897 if(panel.current_panel_bg != "0")
1898 end_pos.y += panel_bg_border * 2;
1900 if(panel_bg_padding)
1902 panel_pos += '1 1 0' * panel_bg_padding;
1903 panel_size -= '2 2 0' * panel_bg_padding;
1907 vector tmp = panel_size;
1910 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1913 if(stat_monsters_total)
1915 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1916 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1920 if(stat_secrets_total)
1922 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1923 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1926 panel_size.x += panel_bg_padding * 2; // restore initial width
1930 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1933 RANKINGS_RECEIVED_CNT = 0;
1934 for (i=RANKINGS_CNT-1; i>=0; --i)
1936 ++RANKINGS_RECEIVED_CNT;
1938 if (RANKINGS_RECEIVED_CNT == 0)
1941 vector hl_rgb = rgb + '0.5 0.5 0.5';
1943 vector scoreboard_selected_hl_pos = pos;
1945 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1946 pos.y += 1.25 * hud_fontsize.y;
1947 if(panel.current_panel_bg != "0")
1948 pos.y += panel_bg_border;
1950 vector scoreboard_selected_hl_size = '0 0 0';
1951 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1952 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1957 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1959 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1964 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1966 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1970 float ranksize = 3 * hud_fontsize.x;
1971 float timesize = 5 * hud_fontsize.x;
1972 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1973 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1974 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1977 rankings_cnt = RANKINGS_RECEIVED_CNT;
1978 rankings_rows = ceil(rankings_cnt / rankings_columns);
1981 // expand name column to fill the entire row
1982 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1983 namesize += available_space;
1984 columnsize.x += available_space;
1986 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1987 panel_size.y += panel_bg_padding * 2;
1988 scoreboard_selected_hl_size.y += panel_size.y;
1992 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1993 if(panel.current_panel_bg != "0")
1994 end_pos.y += panel_bg_border * 2;
1996 if(panel_bg_padding)
1998 panel_pos += '1 1 0' * panel_bg_padding;
1999 panel_size -= '2 2 0' * panel_bg_padding;
2005 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2007 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2009 int column = 0, j = 0;
2010 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2011 int start_item = rankings_start_column * rankings_rows;
2012 for(i = start_item; i < start_item + rankings_cnt; ++i)
2014 int t = grecordtime[i];
2018 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2019 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2020 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2021 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2023 str = count_ordinal(i+1);
2024 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2025 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2026 str = ColorTranslateRGB(grecordholder[i]);
2028 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2029 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2031 pos.y += 1.25 * hud_fontsize.y;
2033 if(j >= rankings_rows)
2037 pos.x += panel_size.x / rankings_columns;
2038 pos.y = panel_pos.y;
2041 strfree(zoned_name_self);
2043 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2045 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2046 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2049 panel_size.x += panel_bg_padding * 2; // restore initial width
2053 bool have_weapon_stats;
2054 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2056 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2058 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2061 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2062 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2068 if (!have_weapon_stats)
2070 FOREACH(Weapons, it != WEP_Null, {
2071 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2072 if (weapon_stats >= 0)
2074 have_weapon_stats = true;
2078 if (!have_weapon_stats)
2085 bool have_item_stats;
2086 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2088 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2090 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2093 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2094 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2100 if (!have_item_stats)
2102 IL_EACH(default_order_items, true, {
2103 if (!is_item_filtered(it))
2105 int q = g_inventory.inv_items[it.m_id];
2106 //q = 1; // debug: display all items
2109 have_item_stats = true;
2114 if (!have_item_stats)
2121 vector Scoreboard_Spectators_Draw(vector pos) {
2126 for(pl = players.sort_next; pl; pl = pl.sort_next)
2128 if(pl.team == NUM_SPECTATOR)
2130 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2131 if(tm.team == NUM_SPECTATOR)
2133 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2134 draw_beginBoldFont();
2135 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2137 pos.y += 1.25 * hud_fontsize.y;
2139 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2140 pos.y += 1.25 * hud_fontsize.y;
2145 if (str != "") // if there's at least one spectator
2146 pos.y += 0.5 * hud_fontsize.y;
2151 void Scoreboard_Draw()
2153 if(!autocvar__hud_configure)
2155 if(!hud_draw_maximized) return;
2157 // frametime checks allow to toggle the scoreboard even when the game is paused
2158 if(scoreboard_active) {
2159 if (scoreboard_fade_alpha == 0)
2160 scoreboard_time = time;
2161 if(hud_configure_menu_open == 1)
2162 scoreboard_fade_alpha = 1;
2163 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2164 if (scoreboard_fadeinspeed && frametime)
2165 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2167 scoreboard_fade_alpha = 1;
2168 if(hud_fontsize_str != autocvar_hud_fontsize)
2170 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2171 Scoreboard_initFieldSizes();
2172 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2176 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2177 if (scoreboard_fadeoutspeed && frametime)
2178 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2180 scoreboard_fade_alpha = 0;
2183 if (!scoreboard_fade_alpha)
2185 scoreboard_acc_fade_alpha = 0;
2186 scoreboard_itemstats_fade_alpha = 0;
2191 scoreboard_fade_alpha = 0;
2193 if (autocvar_hud_panel_scoreboard_dynamichud)
2196 HUD_Scale_Disable();
2198 if(scoreboard_fade_alpha <= 0)
2200 panel_fade_alpha *= scoreboard_fade_alpha;
2201 HUD_Panel_LoadCvars();
2203 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2204 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2205 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2206 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2207 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2208 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2209 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2211 // don't overlap with con_notify
2212 if(!autocvar__hud_configure)
2213 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2215 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2216 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2217 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2218 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2219 panel_pos.x = scoreboard_left;
2220 panel_size.x = fixed_scoreboard_width;
2222 Scoreboard_UpdatePlayerTeams();
2224 scoreboard_top = panel_pos.y;
2225 vector pos = panel_pos;
2230 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2232 // Begin of Game Info Section
2233 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2234 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2236 // Game Info: Game Type
2237 if (scoreboard_ui_enabled == 2)
2238 str = _("Team Selection");
2240 str = MapInfo_Type_ToText(gametype);
2241 draw_beginBoldFont();
2242 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);
2245 pos.y += sb_gameinfo_type_fontsize.y;
2246 // Game Info: Game Detail
2247 if (scoreboard_ui_enabled == 2)
2249 if (scoreboard_selected_team)
2250 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2252 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2253 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);
2255 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2256 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2257 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);
2261 float tl = STAT(TIMELIMIT);
2262 float fl = STAT(FRAGLIMIT);
2263 float ll = STAT(LEADLIMIT);
2264 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2267 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2268 if(!gametype.m_hidelimits)
2273 str = strcat(str, "^7 / "); // delimiter
2276 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2277 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2278 (teamscores_label(ts_primary) == "fastest") ? "" :
2279 TranslateScoresLabel(teamscores_label(ts_primary))));
2283 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2284 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2285 (scores_label(ps_primary) == "fastest") ? "" :
2286 TranslateScoresLabel(scores_label(ps_primary))));
2291 if(tl > 0 || fl > 0)
2294 if (ll_and_fl && fl > 0)
2295 str = strcat(str, "^7 & ");
2297 str = strcat(str, "^7 / ");
2302 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2303 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2304 (teamscores_label(ts_primary) == "fastest") ? "" :
2305 TranslateScoresLabel(teamscores_label(ts_primary))));
2309 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2310 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2311 (scores_label(ps_primary) == "fastest") ? "" :
2312 TranslateScoresLabel(scores_label(ps_primary))));
2316 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
2318 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2319 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2321 // End of Game Info Section
2323 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2324 if(panel.current_panel_bg != "0")
2325 pos.y += panel_bg_border;
2327 // Draw the scoreboard
2328 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2331 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2335 vector panel_bg_color_save = panel_bg_color;
2336 vector team_score_baseoffset;
2337 vector team_size_baseoffset;
2338 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2340 // put team score to the left of scoreboard (and team size to the right)
2341 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2342 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2343 if(panel.current_panel_bg != "0")
2345 team_score_baseoffset.x -= panel_bg_border;
2346 team_size_baseoffset.x += panel_bg_border;
2351 // put team score to the right of scoreboard (and team size to the left)
2352 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2353 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2354 if(panel.current_panel_bg != "0")
2356 team_score_baseoffset.x += panel_bg_border;
2357 team_size_baseoffset.x -= panel_bg_border;
2361 int team_size_total = 0;
2362 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2364 // calculate team size total (sum of all team sizes)
2365 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2366 if(tm.team != NUM_SPECTATOR)
2367 team_size_total += tm.team_size;
2370 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2372 if(tm.team == NUM_SPECTATOR)
2377 draw_beginBoldFont();
2378 vector rgb = Team_ColorRGB(tm.team);
2379 str = ftos(tm.(teamscores(ts_primary)));
2380 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2382 // team score on the left (default)
2383 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2387 // team score on the right
2388 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2390 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2392 // team size (if set to show on the side)
2393 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2395 // calculate the starting position for the whole team size info string
2396 str = sprintf("%d/%d", tm.team_size, team_size_total);
2397 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2399 // team size on the left
2400 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2404 // team size on the right
2405 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2407 str = sprintf("%d", tm.team_size);
2408 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2409 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2410 str = sprintf("/%d", team_size_total);
2411 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2415 // secondary score, e.g. keyhunt
2416 if(ts_primary != ts_secondary)
2418 str = ftos(tm.(teamscores(ts_secondary)));
2419 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2422 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2427 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2430 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2433 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2434 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2435 else if(panel_bg_color_team > 0)
2436 panel_bg_color = rgb * panel_bg_color_team;
2438 panel_bg_color = rgb;
2439 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2441 panel_bg_color = panel_bg_color_save;
2445 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2446 if(tm.team != NUM_SPECTATOR)
2449 // display it anyway
2450 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2453 // draw scoreboard spectators before accuracy and item stats
2454 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2455 pos = Scoreboard_Spectators_Draw(pos);
2458 // draw accuracy and item stats
2459 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2460 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2461 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2462 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2464 // draw scoreboard spectators after accuracy and item stats and before rankings
2465 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2466 pos = Scoreboard_Spectators_Draw(pos);
2469 if(MUTATOR_CALLHOOK(ShowRankings)) {
2470 string ranktitle = M_ARGV(0, string);
2471 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2472 if(race_speedaward_alltimebest)
2475 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2479 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2480 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2481 str = strcat(str, " / ");
2483 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2484 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2485 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2486 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2488 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2493 // draw scoreboard spectators after rankings
2494 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2495 pos = Scoreboard_Spectators_Draw(pos);
2498 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2500 // draw scoreboard spectators after mapstats
2501 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2502 pos = Scoreboard_Spectators_Draw(pos);
2506 // print information about respawn status
2507 float respawn_time = STAT(RESPAWN_TIME);
2511 if(respawn_time < 0)
2513 // a negative number means we are awaiting respawn, time value is still the same
2514 respawn_time *= -1; // remove mark now that we checked it
2516 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2517 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2519 str = sprintf(_("^1Respawning in ^3%s^1..."),
2520 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2521 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2523 count_seconds(ceil(respawn_time - time))
2527 else if(time < respawn_time)
2529 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2530 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2531 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2533 count_seconds(ceil(respawn_time - time))
2537 else if(time >= respawn_time)
2538 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2540 pos.y += 1.2 * hud_fontsize.y;
2541 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2544 pos.y += hud_fontsize.y;
2545 if (scoreboard_fade_alpha < 1)
2546 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2547 else if (pos.y != scoreboard_bottom)
2549 if (pos.y > scoreboard_bottom)
2550 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2552 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2557 if (scoreboard_fade_alpha == 1)
2559 if (scoreboard_bottom > 0.95 * vid_conheight)
2560 rankings_rows = max(1, rankings_rows - 1);
2561 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2562 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2564 rankings_cnt = rankings_rows * rankings_columns;