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;
176 scoreboard_ui_disabled_time = time;
179 // mode: 0 normal, 1 team selection
180 void Scoreboard_UI_Enable(int mode)
184 if (scoreboard_ui_enabled == 2 || !teamplay)
186 // Do not allow reopening the team selection for some time after it has been closed.
187 // It works around a bug caused by the server sending scoreboard_team_selection every frame
188 // until client selects a team and joins: scoreboard_team_selection gets executed even
189 // after client already joined
190 // For the record, with the menu dialog this workaround is not needed because it takes
191 // some time to fade away
192 if (time < scoreboard_ui_disabled_time + 0.5)
194 scoreboard_ui_enabled = 2;
195 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
199 if (scoreboard_ui_enabled == 1)
201 scoreboard_ui_enabled = 1;
202 scoreboard_selected_panel = SB_PANEL_FIRST;
204 scoreboard_selected_player = NULL;
205 scoreboard_selected_team = NULL;
206 scoreboard_selected_panel_time = time;
209 int rankings_start_column;
210 int rankings_rows = 0;
211 int rankings_columns = 0;
212 int rankings_cnt = 0;
213 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
217 if(!scoreboard_ui_enabled)
222 mousepos.x = nPrimary;
223 mousepos.y = nSecondary;
230 // at this point bInputType can only be 0 or 1 (key pressed or released)
231 bool key_pressed = (bInputType == 0);
233 // ESC to exit (TAB-ESC works too)
234 if(nPrimary == K_ESCAPE)
238 HUD_Scoreboard_UI_Disable();
242 // block any input while a menu dialog is fading
243 if(autocvar__menu_alpha)
249 // allow console bind to work
250 string con_keys = findkeysforcommand("toggleconsole", 0);
251 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
253 bool hit_con_bind = false;
255 for (i = 0; i < keys; ++i)
257 if(nPrimary == stof(argv(i)))
262 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
263 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
264 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
265 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
268 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
269 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
270 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
271 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
274 if(nPrimary == K_TAB)
278 if (scoreboard_ui_enabled == 2)
279 goto downarrow_action;
281 ++scoreboard_selected_panel;
282 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
283 ++scoreboard_selected_panel;
284 if (scoreboard_selected_panel >= SB_PANEL_MAX)
285 scoreboard_selected_panel = 1;
287 scoreboard_selected_panel_time = time;
289 else if(nPrimary == K_DOWNARROW)
293 LABEL(downarrow_action);
294 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
296 if (scoreboard_ui_enabled == 2)
298 entity curr_team = NULL;
299 bool scoreboard_selected_team_found = false;
300 if (!scoreboard_selected_team)
301 scoreboard_selected_team_found = true;
303 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
305 if(tm.team == NUM_SPECTATOR)
308 if (scoreboard_selected_team_found)
310 if (scoreboard_selected_team == tm)
311 scoreboard_selected_team_found = true;
314 if (curr_team == scoreboard_selected_team) // loop reached the last team
316 scoreboard_selected_team = curr_team;
321 entity curr_pl = NULL;
322 bool scoreboard_selected_player_found = false;
323 if (!scoreboard_selected_player)
324 scoreboard_selected_player_found = true;
326 for(tm = teams.sort_next; tm; tm = tm.sort_next)
328 if(tm.team != NUM_SPECTATOR)
329 for(pl = players.sort_next; pl; pl = pl.sort_next)
331 if(pl.team != tm.team)
334 if (scoreboard_selected_player_found)
336 if (scoreboard_selected_player == pl)
337 scoreboard_selected_player_found = true;
341 if (curr_pl == scoreboard_selected_player) // loop reached the last player
343 scoreboard_selected_player = curr_pl;
347 else if(nPrimary == K_UPARROW)
351 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
353 if (scoreboard_ui_enabled == 2)
355 entity prev_team = NULL;
356 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
358 if(tm.team == NUM_SPECTATOR)
360 if (tm == scoreboard_selected_team)
365 scoreboard_selected_team = prev_team;
369 entity prev_pl = NULL;
371 for(tm = teams.sort_next; tm; tm = tm.sort_next)
373 if(tm.team != NUM_SPECTATOR)
374 for(pl = players.sort_next; pl; pl = pl.sort_next)
376 if(pl.team != tm.team)
378 if (pl == scoreboard_selected_player)
384 scoreboard_selected_player = prev_pl;
388 else if(nPrimary == K_RIGHTARROW)
392 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
393 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
395 else if(nPrimary == K_LEFTARROW)
399 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
400 rankings_start_column = max(rankings_start_column - 1, 0);
402 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
406 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
408 if (scoreboard_ui_enabled == 2)
411 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
414 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
415 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
416 HUD_Scoreboard_UI_Disable();
418 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
421 HUD_Scoreboard_UI_Disable();
424 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
427 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
431 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
433 switch (scoreboard_selected_columns_layout)
436 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
438 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
439 scoreboard_selected_columns_layout = 1;
444 localcmd(sprintf("scoreboard_columns_set default\n"));
445 scoreboard_selected_columns_layout = 2;
448 localcmd(sprintf("scoreboard_columns_set all\n"));
449 scoreboard_selected_columns_layout = 0;
454 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
458 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
460 if (scoreboard_selected_player)
462 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
463 HUD_Scoreboard_UI_Disable();
467 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
471 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
473 if (scoreboard_selected_player)
474 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
477 else if(hit_con_bind || nPrimary == K_PAUSE)
483 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
484 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
486 #define SB_EXTRA_SORTING_FIELDS 5
487 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
488 void Scoreboard_InitScores()
492 ps_primary = ps_secondary = NULL;
493 ts_primary = ts_secondary = -1;
494 FOREACH(Scores, true, {
495 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
496 if(f == SFL_SORT_PRIO_PRIMARY)
498 if(f == SFL_SORT_PRIO_SECONDARY)
500 if(ps_primary == it || ps_secondary == it)
502 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
503 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
504 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
505 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
506 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
508 if(ps_secondary == NULL)
509 ps_secondary = ps_primary;
511 for(i = 0; i < MAX_TEAMSCORE; ++i)
513 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
514 if(f == SFL_SORT_PRIO_PRIMARY)
516 if(f == SFL_SORT_PRIO_SECONDARY)
519 if(ts_secondary == -1)
520 ts_secondary = ts_primary;
522 Cmd_Scoreboard_SetFields(0);
526 void Scoreboard_UpdatePlayerTeams()
528 static float update_time;
529 if (time <= update_time)
535 for(pl = players.sort_next; pl; pl = pl.sort_next)
538 int Team = entcs_GetScoreTeam(pl.sv_entnum);
539 if(SetTeam(pl, Team))
542 Scoreboard_UpdatePlayerPos(pl);
546 pl = players.sort_next;
551 print(strcat("PNUM: ", ftos(num), "\n"));
556 int Scoreboard_CompareScore(int vl, int vr, int f)
558 TC(int, vl); TC(int, vr); TC(int, f);
559 if(f & SFL_ZERO_IS_WORST)
561 if(vl == 0 && vr != 0)
563 if(vl != 0 && vr == 0)
567 return IS_INCREASING(f);
569 return IS_DECREASING(f);
573 float Scoreboard_ComparePlayerScores(entity left, entity right)
575 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
576 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
583 if(vl == NUM_SPECTATOR)
585 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
587 if(!left.gotscores && right.gotscores)
594 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
598 if (!fld) fld = ps_primary;
599 else if (ps_secondary == ps_primary) continue;
600 else fld = ps_secondary;
604 fld = sb_extra_sorting_field[i];
605 if (fld == ps_primary || fld == ps_secondary) continue;
609 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
610 if (r >= 0) return r;
613 if (left.sv_entnum < right.sv_entnum)
619 void Scoreboard_UpdatePlayerPos(entity player)
622 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
624 SORT_SWAP(player, ent);
626 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
628 SORT_SWAP(ent, player);
632 float Scoreboard_CompareTeamScores(entity left, entity right)
634 if(left.team == NUM_SPECTATOR)
636 if(right.team == NUM_SPECTATOR)
641 for(int i = -2; i < MAX_TEAMSCORE; ++i)
645 if (fld_idx == -1) fld_idx = ts_primary;
646 else if (ts_secondary == ts_primary) continue;
647 else fld_idx = ts_secondary;
652 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
655 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
656 if (r >= 0) return r;
659 if (left.team < right.team)
665 void Scoreboard_UpdateTeamPos(entity Team)
668 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
670 SORT_SWAP(Team, ent);
672 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
674 SORT_SWAP(ent, Team);
678 void Cmd_Scoreboard_Help()
680 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
681 LOG_HELP(_("Usage:"));
682 LOG_HELP("^2scoreboard_columns_set ^3default");
683 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
684 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
685 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
686 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
687 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
688 LOG_HELP(_("The following field names are recognized (case insensitive):"));
694 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
695 "of game types, then a slash, to make the field show up only in these\n"
696 "or in all but these game types. You can also specify 'all' as a\n"
697 "field to show all fields available for the current game mode."));
700 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
701 "include/exclude ALL teams/noteams game modes."));
704 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
705 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
706 "right of the vertical bar aligned to the right."));
707 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
708 "other gamemodes except DM."));
711 // NOTE: adding a gametype with ? to not warn for an optional field
712 // make sure it's excluded in a previous exclusive rule, if any
713 // otherwise the previous exclusive rule warns anyway
714 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
715 #define SCOREBOARD_DEFAULT_COLUMNS \
716 "ping pl fps name |" \
717 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
718 " -teams,lms/deaths +ft,tdm/deaths" \
720 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
721 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
722 " +tdm,ft,dom,ons,as/teamkills"\
723 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
724 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
725 " +lms/lives +lms/rank" \
726 " +kh/kckills +kh/losses +kh/caps" \
727 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
728 " +as/objectives +nb/faults +nb/goals" \
729 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
730 " +dom/ticks +dom/takes" \
731 " -lms,rc,cts,inv,nb/score"
733 void Cmd_Scoreboard_SetFields(int argc)
738 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
742 return; // do nothing, we don't know gametype and scores yet
744 // sbt_fields uses strunzone on the titles!
745 if(!sbt_field_title[0])
746 for(i = 0; i < MAX_SBT_FIELDS; ++i)
747 sbt_field_title[i] = strzone("(null)");
749 // TODO: re enable with gametype dependant cvars?
750 if(argc < 3) // no arguments provided
751 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
754 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
758 if(argv(2) == "default" || argv(2) == "expand_default")
760 if(argv(2) == "expand_default")
761 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
762 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
764 else if(argv(2) == "all" || argv(2) == "ALL")
766 string s = "ping pl name |"; // scores without label (not really scores)
769 // scores without label
770 s = strcat(s, " ", "sum");
771 s = strcat(s, " ", "kdratio");
772 s = strcat(s, " ", "frags");
774 FOREACH(Scores, true, {
776 if(it != ps_secondary)
777 if(scores_label(it) != "")
778 s = strcat(s, " ", scores_label(it));
780 if(ps_secondary != ps_primary)
781 s = strcat(s, " ", scores_label(ps_secondary));
782 s = strcat(s, " ", scores_label(ps_primary));
783 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
790 hud_fontsize = HUD_GetFontsize("hud_fontsize");
792 for(i = 1; i < argc - 1; ++i)
795 bool nocomplain = false;
796 if(substring(str, 0, 1) == "?")
799 str = substring(str, 1, strlen(str) - 1);
802 slash = strstrofs(str, "/", 0);
805 pattern = substring(str, 0, slash);
806 str = substring(str, slash + 1, strlen(str) - (slash + 1));
808 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
812 str = strtolower(str);
813 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
814 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
819 // fields without a label (not networked via the score system)
820 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
821 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
822 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
823 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
824 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
825 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
826 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
827 default: // fields with a label
829 // map alternative labels
830 if (str == "damage") str = "dmg";
831 if (str == "damagetaken") str = "dmgtaken";
833 FOREACH(Scores, true, {
834 if (str == strtolower(scores_label(it))) {
836 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
840 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
841 if(!nocomplain && str != "fps") // server can disable the fps field
842 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
844 strfree(sbt_field_title[sbt_num_fields]);
845 sbt_field_size[sbt_num_fields] = 0;
849 sbt_field[sbt_num_fields] = j;
852 if(j == ps_secondary)
853 have_secondary = true;
858 if(sbt_num_fields >= MAX_SBT_FIELDS)
862 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
864 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
865 have_secondary = true;
866 if(ps_primary == ps_secondary)
867 have_secondary = true;
868 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
870 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
874 strfree(sbt_field_title[sbt_num_fields]);
875 for(i = sbt_num_fields; i > 0; --i)
877 sbt_field_title[i] = sbt_field_title[i-1];
878 sbt_field_size[i] = sbt_field_size[i-1];
879 sbt_field[i] = sbt_field[i-1];
881 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
882 sbt_field[0] = SP_NAME;
884 LOG_INFO("fixed missing field 'name'");
888 strfree(sbt_field_title[sbt_num_fields]);
889 for(i = sbt_num_fields; i > 1; --i)
891 sbt_field_title[i] = sbt_field_title[i-1];
892 sbt_field_size[i] = sbt_field_size[i-1];
893 sbt_field[i] = sbt_field[i-1];
895 sbt_field_title[1] = strzone("|");
896 sbt_field[1] = SP_SEPARATOR;
897 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
899 LOG_INFO("fixed missing field '|'");
902 else if(!have_separator)
904 strcpy(sbt_field_title[sbt_num_fields], "|");
905 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
906 sbt_field[sbt_num_fields] = SP_SEPARATOR;
908 LOG_INFO("fixed missing field '|'");
912 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
913 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
914 sbt_field[sbt_num_fields] = ps_secondary;
916 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
920 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
921 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
922 sbt_field[sbt_num_fields] = ps_primary;
924 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
928 sbt_field[sbt_num_fields] = SP_END;
931 string Scoreboard_AddPlayerId(string pl_name, entity pl)
933 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
934 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
935 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
939 vector sbt_field_rgb;
940 string sbt_field_icon0;
941 string sbt_field_icon1;
942 string sbt_field_icon2;
943 vector sbt_field_icon0_rgb;
944 vector sbt_field_icon1_rgb;
945 vector sbt_field_icon2_rgb;
946 string Scoreboard_GetName(entity pl)
948 if(ready_waiting && pl.ready)
950 sbt_field_icon0 = "gfx/scoreboard/player_ready";
954 int f = entcs_GetClientColors(pl.sv_entnum);
956 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
957 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
958 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
959 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
960 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
963 return entcs_GetName(pl.sv_entnum);
966 string Scoreboard_GetField(entity pl, PlayerScoreField field)
968 float tmp, num, denom;
971 sbt_field_rgb = '1 1 1';
972 sbt_field_icon0 = "";
973 sbt_field_icon1 = "";
974 sbt_field_icon2 = "";
975 sbt_field_icon0_rgb = '1 1 1';
976 sbt_field_icon1_rgb = '1 1 1';
977 sbt_field_icon2_rgb = '1 1 1';
982 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
983 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
987 tmp = max(0, min(220, f-80)) / 220;
988 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
994 f = pl.ping_packetloss;
995 tmp = pl.ping_movementloss;
996 if(f == 0 && tmp == 0)
998 str = ftos(ceil(f * 100));
1000 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1001 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1002 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1006 str = Scoreboard_GetName(pl);
1007 if (autocvar_hud_panel_scoreboard_playerid)
1008 str = Scoreboard_AddPlayerId(str, pl);
1012 f = pl.(scores(SP_KILLS));
1013 f -= pl.(scores(SP_SUICIDES));
1017 num = pl.(scores(SP_KILLS));
1018 denom = pl.(scores(SP_DEATHS));
1021 sbt_field_rgb = '0 1 0';
1022 str = sprintf("%d", num);
1023 } else if(num <= 0) {
1024 sbt_field_rgb = '1 0 0';
1025 str = sprintf("%.1f", num/denom);
1027 str = sprintf("%.1f", num/denom);
1031 f = pl.(scores(SP_KILLS));
1032 f -= pl.(scores(SP_DEATHS));
1035 sbt_field_rgb = '0 1 0';
1037 sbt_field_rgb = '1 1 1';
1039 sbt_field_rgb = '1 0 0';
1045 float elo = pl.(scores(SP_ELO));
1047 case -1: return "...";
1048 case -2: return _("N/A");
1049 default: return ftos(elo);
1055 float fps = pl.(scores(SP_FPS));
1058 sbt_field_rgb = '1 1 1';
1059 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1061 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1062 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1066 case SP_DMG: case SP_DMGTAKEN:
1067 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1069 default: case SP_SCORE:
1070 tmp = pl.(scores(field));
1071 f = scores_flags(field);
1072 if(field == ps_primary)
1073 sbt_field_rgb = '1 1 0';
1074 else if(field == ps_secondary)
1075 sbt_field_rgb = '0 1 1';
1077 sbt_field_rgb = '1 1 1';
1078 return ScoreString(f, tmp);
1083 float sbt_fixcolumnwidth_len;
1084 float sbt_fixcolumnwidth_iconlen;
1085 float sbt_fixcolumnwidth_marginlen;
1087 string Scoreboard_FixColumnWidth(int i, string str)
1093 sbt_fixcolumnwidth_iconlen = 0;
1095 if(sbt_field_icon0 != "")
1097 sz = draw_getimagesize(sbt_field_icon0);
1099 if(sbt_fixcolumnwidth_iconlen < f)
1100 sbt_fixcolumnwidth_iconlen = f;
1103 if(sbt_field_icon1 != "")
1105 sz = draw_getimagesize(sbt_field_icon1);
1107 if(sbt_fixcolumnwidth_iconlen < f)
1108 sbt_fixcolumnwidth_iconlen = f;
1111 if(sbt_field_icon2 != "")
1113 sz = draw_getimagesize(sbt_field_icon2);
1115 if(sbt_fixcolumnwidth_iconlen < f)
1116 sbt_fixcolumnwidth_iconlen = f;
1119 if(sbt_fixcolumnwidth_iconlen != 0)
1121 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1122 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1125 sbt_fixcolumnwidth_marginlen = 0;
1127 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1130 float remaining_space = 0;
1131 for(j = 0; j < sbt_num_fields; ++j)
1133 if (sbt_field[i] != SP_SEPARATOR)
1134 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1135 sbt_field_size[i] = panel_size.x - remaining_space;
1137 if (sbt_fixcolumnwidth_iconlen != 0)
1138 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1139 float namesize = panel_size.x - remaining_space;
1140 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1141 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1143 max_namesize = vid_conwidth - remaining_space;
1146 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1148 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1149 if(sbt_field_size[i] < f)
1150 sbt_field_size[i] = f;
1155 void Scoreboard_initFieldSizes()
1157 for(int i = 0; i < sbt_num_fields; ++i)
1159 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1160 Scoreboard_FixColumnWidth(i, "");
1164 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1167 vector column_dim = eY * panel_size.y;
1169 column_dim.y -= 1.25 * hud_fontsize.y;
1170 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1171 pos.x += hud_fontsize.x * 0.5;
1172 for(i = 0; i < sbt_num_fields; ++i)
1174 if(sbt_field[i] == SP_SEPARATOR)
1176 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1179 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1180 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1181 pos.x += column_dim.x;
1183 if(sbt_field[i] == SP_SEPARATOR)
1185 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1186 for(i = sbt_num_fields - 1; i > 0; --i)
1188 if(sbt_field[i] == SP_SEPARATOR)
1191 pos.x -= sbt_field_size[i];
1196 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1197 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1200 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1201 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1202 pos.x -= hud_fontsize.x;
1206 pos.x = panel_pos.x;
1207 pos.y += 1.25 * hud_fontsize.y;
1211 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1213 TC(bool, is_self); TC(int, pl_number);
1215 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1217 vector h_pos = item_pos;
1218 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1219 // alternated rows highlighting
1220 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1222 if (pl == scoreboard_selected_player)
1223 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1226 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1227 else if((sbt_highlight) && (!(pl_number % 2)))
1228 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1230 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1232 vector pos = item_pos;
1233 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1235 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1237 pos.x += hud_fontsize.x * 0.5;
1238 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1239 vector tmp = '0 0 0';
1241 PlayerScoreField field;
1242 for(i = 0; i < sbt_num_fields; ++i)
1244 field = sbt_field[i];
1245 if(field == SP_SEPARATOR)
1248 if(is_spec && field != SP_NAME && field != SP_PING) {
1249 pos.x += sbt_field_size[i] + hud_fontsize.x;
1252 str = Scoreboard_GetField(pl, field);
1253 str = Scoreboard_FixColumnWidth(i, str);
1255 pos.x += sbt_field_size[i] + hud_fontsize.x;
1257 if(field == SP_NAME) {
1258 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1259 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1261 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1262 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1265 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1266 if(sbt_field_icon0 != "")
1267 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1268 if(sbt_field_icon1 != "")
1269 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1270 if(sbt_field_icon2 != "")
1271 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1274 if(sbt_field[i] == SP_SEPARATOR)
1276 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1277 for(i = sbt_num_fields-1; i > 0; --i)
1279 field = sbt_field[i];
1280 if(field == SP_SEPARATOR)
1283 if(is_spec && field != SP_NAME && field != SP_PING) {
1284 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1288 str = Scoreboard_GetField(pl, field);
1289 str = Scoreboard_FixColumnWidth(i, str);
1291 if(field == SP_NAME) {
1292 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1293 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1295 tmp.x = sbt_fixcolumnwidth_len;
1296 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1299 tmp.x = sbt_field_size[i];
1300 if(sbt_field_icon0 != "")
1301 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1302 if(sbt_field_icon1 != "")
1303 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1304 if(sbt_field_icon2 != "")
1305 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1306 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1311 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1314 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1317 vector h_pos = item_pos;
1318 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1320 bool complete = (this_team == NUM_SPECTATOR);
1323 if((sbt_highlight) && (!(pl_number % 2)))
1324 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1326 vector pos = item_pos;
1327 pos.x += hud_fontsize.x * 0.5;
1328 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1330 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1332 width_limit -= stringwidth("...", false, hud_fontsize);
1333 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1334 static float max_name_width = 0;
1336 float fieldsize = 0;
1337 float min_fieldsize = 0;
1338 float fieldpadding = hud_fontsize.x * 0.25;
1339 if(this_team == NUM_SPECTATOR)
1341 if(autocvar_hud_panel_scoreboard_spectators_showping)
1342 min_fieldsize = stringwidth("999", false, hud_fontsize);
1344 else if(autocvar_hud_panel_scoreboard_others_showscore)
1345 min_fieldsize = stringwidth("99", false, hud_fontsize);
1346 for(i = 0; pl; pl = pl.sort_next)
1348 if(pl.team != this_team)
1350 if(pl == ignored_pl)
1354 if(this_team == NUM_SPECTATOR)
1356 if(autocvar_hud_panel_scoreboard_spectators_showping)
1357 field = Scoreboard_GetField(pl, SP_PING);
1359 else if(autocvar_hud_panel_scoreboard_others_showscore)
1360 field = Scoreboard_GetField(pl, SP_SCORE);
1362 string str = entcs_GetName(pl.sv_entnum);
1363 if (autocvar_hud_panel_scoreboard_playerid)
1364 str = Scoreboard_AddPlayerId(str, pl);
1365 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1366 float column_width = stringwidth(str, true, hud_fontsize);
1367 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1369 if(column_width > max_name_width)
1370 max_name_width = column_width;
1371 column_width = max_name_width;
1375 fieldsize = stringwidth(field, false, hud_fontsize);
1376 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1379 if(pos.x + column_width > width_limit)
1384 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1389 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1390 pos.y += hud_fontsize.y * 1.25;
1394 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1396 if (pl == scoreboard_selected_player)
1398 h_size.x = column_width + hud_fontsize.x * 0.25;
1399 h_size.y = hud_fontsize.y;
1400 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1404 vector name_pos = pos;
1405 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1406 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1407 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1410 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1411 h_size.y = hud_fontsize.y;
1412 vector field_pos = pos;
1413 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1414 field_pos.x += column_width - h_size.x;
1416 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1417 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1418 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1422 h_size.x = column_width + hud_fontsize.x * 0.25;
1423 h_size.y = hud_fontsize.y;
1424 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1426 pos.x += column_width;
1427 pos.x += hud_fontsize.x;
1429 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1432 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1434 int max_players = 999;
1435 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1437 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1440 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1441 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1442 height /= team_count;
1445 height -= panel_bg_padding * 2; // - padding
1446 max_players = floor(height / (hud_fontsize.y * 1.25));
1447 if(max_players <= 1)
1449 if(max_players == tm.team_size)
1454 entity me = playerslots[current_player];
1456 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1457 panel_size.y += panel_bg_padding * 2;
1459 vector scoreboard_selected_hl_pos = pos;
1460 vector scoreboard_selected_hl_size = '0 0 0';
1461 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1462 scoreboard_selected_hl_size.y = panel_size.y;
1466 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1467 if(panel.current_panel_bg != "0")
1468 end_pos.y += panel_bg_border * 2;
1470 if(panel_bg_padding)
1472 panel_pos += '1 1 0' * panel_bg_padding;
1473 panel_size -= '2 2 0' * panel_bg_padding;
1477 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1481 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1483 pos.y += 1.25 * hud_fontsize.y;
1486 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1488 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1491 // print header row and highlight columns
1492 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1494 // fill the table and draw the rows
1495 bool is_self = false;
1496 bool self_shown = false;
1498 for(pl = players.sort_next; pl; pl = pl.sort_next)
1500 if(pl.team != tm.team)
1502 if(i == max_players - 2 && pl != me)
1504 if(!self_shown && me.team == tm.team)
1506 Scoreboard_DrawItem(pos, rgb, me, true, i);
1508 pos.y += 1.25 * hud_fontsize.y;
1512 if(i >= max_players - 1)
1514 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1517 is_self = (pl.sv_entnum == current_player);
1518 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1521 pos.y += 1.25 * hud_fontsize.y;
1525 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1527 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1529 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1530 _alpha *= panel_fg_alpha;
1532 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1536 panel_size.x += panel_bg_padding * 2; // restore initial width
1540 bool Scoreboard_WouldDraw()
1542 if (scoreboard_ui_enabled)
1544 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1546 else if (QuickMenu_IsOpened())
1548 else if (HUD_Radar_Clickable())
1550 else if (scoreboard_showscores)
1552 else if (intermission == 1)
1554 else if (intermission == 2)
1556 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1557 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1561 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1566 float average_accuracy;
1567 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1569 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1571 WepSet weapons_stat = WepSet_GetFromStat();
1572 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1573 int disownedcnt = 0;
1575 FOREACH(Weapons, it != WEP_Null, {
1576 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1578 WepSet set = it.m_wepset;
1579 if(it.spawnflags & WEP_TYPE_OTHER)
1584 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1586 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1593 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1594 if (weapon_cnt <= 0) return pos;
1597 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1599 int columns = ceil(weapon_cnt / rows);
1601 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1602 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1603 float height = weapon_height + hud_fontsize.y;
1605 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);
1606 pos.y += 1.25 * hud_fontsize.y;
1607 if(panel.current_panel_bg != "0")
1608 pos.y += panel_bg_border;
1611 panel_size.y = height * rows;
1612 panel_size.y += panel_bg_padding * 2;
1614 float panel_bg_alpha_save = panel_bg_alpha;
1615 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1617 panel_bg_alpha = panel_bg_alpha_save;
1619 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1620 if(panel.current_panel_bg != "0")
1621 end_pos.y += panel_bg_border * 2;
1623 if(panel_bg_padding)
1625 panel_pos += '1 1 0' * panel_bg_padding;
1626 panel_size -= '2 2 0' * panel_bg_padding;
1630 vector tmp = panel_size;
1632 float weapon_width = tmp.x / columns / rows;
1635 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1639 // column highlighting
1640 for (int i = 0; i < columns; ++i)
1642 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);
1645 for (int i = 0; i < rows; ++i)
1646 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1649 average_accuracy = 0;
1650 int weapons_with_stats = 0;
1652 pos.x += weapon_width / 2;
1654 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1657 Accuracy_LoadColors();
1659 float oldposx = pos.x;
1663 FOREACH(Weapons, it != WEP_Null, {
1664 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1666 WepSet set = it.m_wepset;
1667 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1669 if (it.spawnflags & WEP_TYPE_OTHER)
1673 if (weapon_stats >= 0)
1674 weapon_alpha = sbt_fg_alpha;
1676 weapon_alpha = 0.2 * sbt_fg_alpha;
1679 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1681 if (weapon_stats >= 0) {
1682 weapons_with_stats += 1;
1683 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1685 string s = sprintf("%d%%", weapon_stats * 100);
1686 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1688 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1689 rgb = Accuracy_GetColor(weapon_stats);
1691 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1693 tmpos.x += weapon_width * rows;
1694 pos.x += weapon_width * rows;
1695 if (rows == 2 && column == columns - 1) {
1703 if (weapons_with_stats)
1704 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1706 panel_size.x += panel_bg_padding * 2; // restore initial width
1711 bool is_item_filtered(entity it)
1713 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1715 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1718 if (it.instanceOfArmor || it.instanceOfHealth)
1720 int ha_mask = floor(mask) % 10;
1723 default: return false;
1724 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1725 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1726 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1727 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1730 if (it.instanceOfAmmo)
1732 int ammo_mask = floor(mask / 10) % 10;
1733 return (ammo_mask == 1);
1738 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1740 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1742 int disowned_cnt = 0;
1743 int uninteresting_cnt = 0;
1744 IL_EACH(default_order_items, true, {
1745 int q = g_inventory.inv_items[it.m_id];
1746 //q = 1; // debug: display all items
1747 if (is_item_filtered(it))
1748 ++uninteresting_cnt;
1752 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1753 int n = items_cnt - disowned_cnt;
1754 if (n <= 0) return pos;
1756 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1757 int columns = max(6, ceil(n / rows));
1759 float item_height = hud_fontsize.y * 2.3;
1760 float height = item_height + hud_fontsize.y;
1762 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1763 pos.y += 1.25 * hud_fontsize.y;
1764 if(panel.current_panel_bg != "0")
1765 pos.y += panel_bg_border;
1768 panel_size.y = height * rows;
1769 panel_size.y += panel_bg_padding * 2;
1771 float panel_bg_alpha_save = panel_bg_alpha;
1772 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1774 panel_bg_alpha = panel_bg_alpha_save;
1776 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1777 if(panel.current_panel_bg != "0")
1778 end_pos.y += panel_bg_border * 2;
1780 if(panel_bg_padding)
1782 panel_pos += '1 1 0' * panel_bg_padding;
1783 panel_size -= '2 2 0' * panel_bg_padding;
1787 vector tmp = panel_size;
1789 float item_width = tmp.x / columns / rows;
1792 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1796 // column highlighting
1797 for (int i = 0; i < columns; ++i)
1799 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);
1802 for (int i = 0; i < rows; ++i)
1803 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1807 pos.x += item_width / 2;
1809 float oldposx = pos.x;
1813 IL_EACH(default_order_items, !is_item_filtered(it), {
1814 int n = g_inventory.inv_items[it.m_id];
1815 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1816 if (n <= 0) continue;
1817 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);
1819 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1820 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1821 tmpos.x += item_width * rows;
1822 pos.x += item_width * rows;
1823 if (rows == 2 && column == columns - 1) {
1831 panel_size.x += panel_bg_padding * 2; // restore initial width
1836 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1838 pos.x += hud_fontsize.x * 0.25;
1839 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1840 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1841 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1843 pos.y += hud_fontsize.y;
1848 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1849 float stat_secrets_found, stat_secrets_total;
1850 float stat_monsters_killed, stat_monsters_total;
1854 // get monster stats
1855 stat_monsters_killed = STAT(MONSTERS_KILLED);
1856 stat_monsters_total = STAT(MONSTERS_TOTAL);
1858 // get secrets stats
1859 stat_secrets_found = STAT(SECRETS_FOUND);
1860 stat_secrets_total = STAT(SECRETS_TOTAL);
1862 // get number of rows
1863 if(stat_secrets_total)
1865 if(stat_monsters_total)
1868 // if no rows, return
1872 // draw table header
1873 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1874 pos.y += 1.25 * hud_fontsize.y;
1875 if(panel.current_panel_bg != "0")
1876 pos.y += panel_bg_border;
1879 panel_size.y = hud_fontsize.y * rows;
1880 panel_size.y += panel_bg_padding * 2;
1883 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1884 if(panel.current_panel_bg != "0")
1885 end_pos.y += panel_bg_border * 2;
1887 if(panel_bg_padding)
1889 panel_pos += '1 1 0' * panel_bg_padding;
1890 panel_size -= '2 2 0' * panel_bg_padding;
1894 vector tmp = panel_size;
1897 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1900 if(stat_monsters_total)
1902 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1903 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1907 if(stat_secrets_total)
1909 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1910 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1913 panel_size.x += panel_bg_padding * 2; // restore initial width
1917 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1920 RANKINGS_RECEIVED_CNT = 0;
1921 for (i=RANKINGS_CNT-1; i>=0; --i)
1923 ++RANKINGS_RECEIVED_CNT;
1925 if (RANKINGS_RECEIVED_CNT == 0)
1928 vector hl_rgb = rgb + '0.5 0.5 0.5';
1930 vector scoreboard_selected_hl_pos = pos;
1932 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1933 pos.y += 1.25 * hud_fontsize.y;
1934 if(panel.current_panel_bg != "0")
1935 pos.y += panel_bg_border;
1937 vector scoreboard_selected_hl_size = '0 0 0';
1938 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1939 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1944 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1946 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1951 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1953 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1957 float ranksize = 3 * hud_fontsize.x;
1958 float timesize = 5 * hud_fontsize.x;
1959 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1960 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1961 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1964 rankings_cnt = RANKINGS_RECEIVED_CNT;
1965 rankings_rows = ceil(rankings_cnt / rankings_columns);
1968 // expand name column to fill the entire row
1969 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1970 namesize += available_space;
1971 columnsize.x += available_space;
1973 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1974 panel_size.y += panel_bg_padding * 2;
1975 scoreboard_selected_hl_size.y += panel_size.y;
1979 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1980 if(panel.current_panel_bg != "0")
1981 end_pos.y += panel_bg_border * 2;
1983 if(panel_bg_padding)
1985 panel_pos += '1 1 0' * panel_bg_padding;
1986 panel_size -= '2 2 0' * panel_bg_padding;
1992 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1994 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1996 int column = 0, j = 0;
1997 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1998 int start_item = rankings_start_column * rankings_rows;
1999 for(i = start_item; i < start_item + rankings_cnt; ++i)
2001 int t = grecordtime[i];
2005 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2006 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2007 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2008 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2010 str = count_ordinal(i+1);
2011 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2012 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2013 str = ColorTranslateRGB(grecordholder[i]);
2015 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2016 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2018 pos.y += 1.25 * hud_fontsize.y;
2020 if(j >= rankings_rows)
2024 pos.x += panel_size.x / rankings_columns;
2025 pos.y = panel_pos.y;
2028 strfree(zoned_name_self);
2030 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2032 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2033 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2036 panel_size.x += panel_bg_padding * 2; // restore initial width
2040 bool have_weapon_stats;
2041 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2043 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2045 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2048 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2049 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2055 if (!have_weapon_stats)
2057 FOREACH(Weapons, it != WEP_Null, {
2058 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2059 if (weapon_stats >= 0)
2061 have_weapon_stats = true;
2065 if (!have_weapon_stats)
2072 bool have_item_stats;
2073 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2075 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2077 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2080 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2081 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2087 if (!have_item_stats)
2089 IL_EACH(default_order_items, true, {
2090 if (!is_item_filtered(it))
2092 int q = g_inventory.inv_items[it.m_id];
2093 //q = 1; // debug: display all items
2096 have_item_stats = true;
2101 if (!have_item_stats)
2108 vector Scoreboard_Spectators_Draw(vector pos) {
2113 for(pl = players.sort_next; pl; pl = pl.sort_next)
2115 if(pl.team == NUM_SPECTATOR)
2117 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2118 if(tm.team == NUM_SPECTATOR)
2120 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2121 draw_beginBoldFont();
2122 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2124 pos.y += 1.25 * hud_fontsize.y;
2126 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2127 pos.y += 1.25 * hud_fontsize.y;
2132 if (str != "") // if there's at least one spectator
2133 pos.y += 0.5 * hud_fontsize.y;
2138 void Scoreboard_Draw()
2140 if(!autocvar__hud_configure)
2142 if(!hud_draw_maximized) return;
2144 // frametime checks allow to toggle the scoreboard even when the game is paused
2145 if(scoreboard_active) {
2146 if (scoreboard_fade_alpha == 0)
2147 scoreboard_time = time;
2148 if(hud_configure_menu_open == 1)
2149 scoreboard_fade_alpha = 1;
2150 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2151 if (scoreboard_fadeinspeed && frametime)
2152 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2154 scoreboard_fade_alpha = 1;
2155 if(hud_fontsize_str != autocvar_hud_fontsize)
2157 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2158 Scoreboard_initFieldSizes();
2159 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2163 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2164 if (scoreboard_fadeoutspeed && frametime)
2165 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2167 scoreboard_fade_alpha = 0;
2170 if (!scoreboard_fade_alpha)
2172 scoreboard_acc_fade_alpha = 0;
2173 scoreboard_itemstats_fade_alpha = 0;
2178 scoreboard_fade_alpha = 0;
2180 if (autocvar_hud_panel_scoreboard_dynamichud)
2183 HUD_Scale_Disable();
2185 if(scoreboard_fade_alpha <= 0)
2187 panel_fade_alpha *= scoreboard_fade_alpha;
2188 HUD_Panel_LoadCvars();
2190 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2191 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2192 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2193 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2194 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2195 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2196 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2198 // don't overlap with con_notify
2199 if(!autocvar__hud_configure)
2200 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2202 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2203 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2204 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2205 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2206 panel_pos.x = scoreboard_left;
2207 panel_size.x = fixed_scoreboard_width;
2209 Scoreboard_UpdatePlayerTeams();
2211 scoreboard_top = panel_pos.y;
2212 vector pos = panel_pos;
2217 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2219 // Begin of Game Info Section
2220 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2221 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2223 // Game Info: Game Type
2224 if (scoreboard_ui_enabled == 2)
2225 str = _("Team Selection");
2227 str = MapInfo_Type_ToText(gametype);
2228 draw_beginBoldFont();
2229 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);
2232 pos.y += sb_gameinfo_type_fontsize.y;
2233 // Game Info: Game Detail
2234 if (scoreboard_ui_enabled == 2)
2236 if (scoreboard_selected_team)
2237 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2239 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
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);
2242 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2243 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2244 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);
2248 float tl = STAT(TIMELIMIT);
2249 float fl = STAT(FRAGLIMIT);
2250 float ll = STAT(LEADLIMIT);
2251 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2254 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2255 if(!gametype.m_hidelimits)
2260 str = strcat(str, "^7 / "); // delimiter
2263 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2264 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2265 (teamscores_label(ts_primary) == "fastest") ? "" :
2266 TranslateScoresLabel(teamscores_label(ts_primary))));
2270 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2271 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2272 (scores_label(ps_primary) == "fastest") ? "" :
2273 TranslateScoresLabel(scores_label(ps_primary))));
2278 if(tl > 0 || fl > 0)
2281 if (ll_and_fl && fl > 0)
2282 str = strcat(str, "^7 & ");
2284 str = strcat(str, "^7 / ");
2289 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2290 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2291 (teamscores_label(ts_primary) == "fastest") ? "" :
2292 TranslateScoresLabel(teamscores_label(ts_primary))));
2296 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2297 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2298 (scores_label(ps_primary) == "fastest") ? "" :
2299 TranslateScoresLabel(scores_label(ps_primary))));
2303 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
2305 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2306 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2308 // End of Game Info Section
2310 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2311 if(panel.current_panel_bg != "0")
2312 pos.y += panel_bg_border;
2314 // Draw the scoreboard
2315 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2318 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2322 vector panel_bg_color_save = panel_bg_color;
2323 vector team_score_baseoffset;
2324 vector team_size_baseoffset;
2325 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2327 // put team score to the left of scoreboard (and team size to the right)
2328 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2329 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2330 if(panel.current_panel_bg != "0")
2332 team_score_baseoffset.x -= panel_bg_border;
2333 team_size_baseoffset.x += panel_bg_border;
2338 // put team score to the right of scoreboard (and team size to the left)
2339 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2340 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2341 if(panel.current_panel_bg != "0")
2343 team_score_baseoffset.x += panel_bg_border;
2344 team_size_baseoffset.x -= panel_bg_border;
2348 int team_size_total = 0;
2349 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2351 // calculate team size total (sum of all team sizes)
2352 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2353 if(tm.team != NUM_SPECTATOR)
2354 team_size_total += tm.team_size;
2357 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2359 if(tm.team == NUM_SPECTATOR)
2364 draw_beginBoldFont();
2365 vector rgb = Team_ColorRGB(tm.team);
2366 str = ftos(tm.(teamscores(ts_primary)));
2367 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2369 // team score on the left (default)
2370 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2374 // team score on the right
2375 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2377 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2379 // team size (if set to show on the side)
2380 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2382 // calculate the starting position for the whole team size info string
2383 str = sprintf("%d/%d", tm.team_size, team_size_total);
2384 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2386 // team size on the left
2387 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2391 // team size on the right
2392 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2394 str = sprintf("%d", tm.team_size);
2395 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2396 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2397 str = sprintf("/%d", team_size_total);
2398 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2402 // secondary score, e.g. keyhunt
2403 if(ts_primary != ts_secondary)
2405 str = ftos(tm.(teamscores(ts_secondary)));
2406 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2409 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2414 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2417 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2420 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2421 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2422 else if(panel_bg_color_team > 0)
2423 panel_bg_color = rgb * panel_bg_color_team;
2425 panel_bg_color = rgb;
2426 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2428 panel_bg_color = panel_bg_color_save;
2432 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2433 if(tm.team != NUM_SPECTATOR)
2436 // display it anyway
2437 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2440 // draw scoreboard spectators before accuracy and item stats
2441 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2442 pos = Scoreboard_Spectators_Draw(pos);
2445 // draw accuracy and item stats
2446 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2447 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2448 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2449 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2451 // draw scoreboard spectators after accuracy and item stats and before rankings
2452 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2453 pos = Scoreboard_Spectators_Draw(pos);
2456 if(MUTATOR_CALLHOOK(ShowRankings)) {
2457 string ranktitle = M_ARGV(0, string);
2458 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2459 if(race_speedaward_alltimebest)
2462 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2466 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2467 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2468 str = strcat(str, " / ");
2470 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2471 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2472 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2473 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2475 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2480 // draw scoreboard spectators after rankings
2481 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2482 pos = Scoreboard_Spectators_Draw(pos);
2485 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2487 // draw scoreboard spectators after mapstats
2488 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2489 pos = Scoreboard_Spectators_Draw(pos);
2493 // print information about respawn status
2494 float respawn_time = STAT(RESPAWN_TIME);
2498 if(respawn_time < 0)
2500 // a negative number means we are awaiting respawn, time value is still the same
2501 respawn_time *= -1; // remove mark now that we checked it
2503 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2504 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2506 str = sprintf(_("^1Respawning in ^3%s^1..."),
2507 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2508 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2510 count_seconds(ceil(respawn_time - time))
2514 else if(time < respawn_time)
2516 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2517 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2518 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2520 count_seconds(ceil(respawn_time - time))
2524 else if(time >= respawn_time)
2525 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2527 pos.y += 1.2 * hud_fontsize.y;
2528 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2531 pos.y += hud_fontsize.y;
2532 if (scoreboard_fade_alpha < 1)
2533 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2534 else if (pos.y != scoreboard_bottom)
2536 if (pos.y > scoreboard_bottom)
2537 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2539 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2544 if (scoreboard_fade_alpha == 1)
2546 if (scoreboard_bottom > 0.95 * vid_conheight)
2547 rankings_rows = max(1, rankings_rows - 1);
2548 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2549 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2551 rankings_cnt = rankings_rows * rankings_columns;