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 bool scoreboard_ui_disabling;
170 void HUD_Scoreboard_UI_Disable()
172 scoreboard_ui_disabling = true;
173 scoreboard_showscores = false;
176 void HUD_Scoreboard_UI_Disable_Instantly()
178 scoreboard_ui_disabling = false;
179 scoreboard_ui_enabled = 0;
180 scoreboard_selected_panel = 0;
181 scoreboard_selected_player = NULL;
182 scoreboard_selected_team = NULL;
185 // mode: 0 normal, 1 team selection
186 void Scoreboard_UI_Enable(int mode)
190 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
193 // release player's pressed keys as they aren't released elsewhere
194 // in particular jump needs to be released as it may open the team selection
195 // (when server detects jump has been pressed it sends the command to open the team selection)
196 Release_Common_Keys();
197 scoreboard_ui_enabled = 2;
198 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
202 if (scoreboard_ui_enabled == 1)
204 scoreboard_ui_enabled = 1;
205 scoreboard_selected_panel = SB_PANEL_FIRST;
207 scoreboard_selected_player = NULL;
208 scoreboard_selected_team = NULL;
209 scoreboard_selected_panel_time = time;
212 int rankings_start_column;
213 int rankings_rows = 0;
214 int rankings_columns = 0;
215 int rankings_cnt = 0;
216 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
220 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
225 mousepos.x = nPrimary;
226 mousepos.y = nSecondary;
233 // at this point bInputType can only be 0 or 1 (key pressed or released)
234 bool key_pressed = (bInputType == 0);
236 // ESC to exit (TAB-ESC works too)
237 if(nPrimary == K_ESCAPE)
241 HUD_Scoreboard_UI_Disable();
245 // block any input while a menu dialog is fading
246 if(autocvar__menu_alpha)
252 // allow console bind to work
253 string con_keys = findkeysforcommand("toggleconsole", 0);
254 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
256 bool hit_con_bind = false;
258 for (i = 0; i < keys; ++i)
260 if(nPrimary == stof(argv(i)))
265 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
266 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
267 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
268 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
271 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
272 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
273 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
274 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
277 if(nPrimary == K_TAB)
281 if (scoreboard_ui_enabled == 2)
283 if (hudShiftState & S_SHIFT)
286 goto downarrow_action;
289 if (hudShiftState & S_SHIFT)
291 --scoreboard_selected_panel;
292 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
293 --scoreboard_selected_panel;
294 if (scoreboard_selected_panel < SB_PANEL_FIRST)
295 scoreboard_selected_panel = SB_PANEL_MAX;
299 ++scoreboard_selected_panel;
300 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
301 ++scoreboard_selected_panel;
302 if (scoreboard_selected_panel > SB_PANEL_MAX)
303 scoreboard_selected_panel = SB_PANEL_FIRST;
306 scoreboard_selected_panel_time = time;
308 else if(nPrimary == K_DOWNARROW)
312 LABEL(downarrow_action);
313 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
315 if (scoreboard_ui_enabled == 2)
317 entity curr_team = NULL;
318 bool scoreboard_selected_team_found = false;
319 if (!scoreboard_selected_team)
320 scoreboard_selected_team_found = true;
322 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
324 if(tm.team == NUM_SPECTATOR)
327 if (scoreboard_selected_team_found)
329 if (scoreboard_selected_team == tm)
330 scoreboard_selected_team_found = true;
333 if (curr_team == scoreboard_selected_team) // loop reached the last team
335 scoreboard_selected_team = curr_team;
340 entity curr_pl = NULL;
341 bool scoreboard_selected_player_found = false;
342 if (!scoreboard_selected_player)
343 scoreboard_selected_player_found = true;
345 for(tm = teams.sort_next; tm; tm = tm.sort_next)
347 if(tm.team != NUM_SPECTATOR)
348 for(pl = players.sort_next; pl; pl = pl.sort_next)
350 if(pl.team != tm.team)
353 if (scoreboard_selected_player_found)
355 if (scoreboard_selected_player == pl)
356 scoreboard_selected_player_found = true;
360 if (curr_pl == scoreboard_selected_player) // loop reached the last player
362 scoreboard_selected_player = curr_pl;
366 else if(nPrimary == K_UPARROW)
370 LABEL(uparrow_action);
371 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
373 if (scoreboard_ui_enabled == 2)
375 entity prev_team = NULL;
376 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
378 if(tm.team == NUM_SPECTATOR)
380 if (tm == scoreboard_selected_team)
385 scoreboard_selected_team = prev_team;
389 entity prev_pl = NULL;
391 for(tm = teams.sort_next; tm; tm = tm.sort_next)
393 if(tm.team != NUM_SPECTATOR)
394 for(pl = players.sort_next; pl; pl = pl.sort_next)
396 if(pl.team != tm.team)
398 if (pl == scoreboard_selected_player)
404 scoreboard_selected_player = prev_pl;
408 else if(nPrimary == K_RIGHTARROW)
412 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
413 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
415 else if(nPrimary == K_LEFTARROW)
419 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
420 rankings_start_column = max(rankings_start_column - 1, 0);
422 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
426 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
428 if (scoreboard_ui_enabled == 2)
431 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
434 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
435 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
436 HUD_Scoreboard_UI_Disable();
438 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
441 HUD_Scoreboard_UI_Disable();
444 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
447 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
451 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
453 switch (scoreboard_selected_columns_layout)
456 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
458 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
459 scoreboard_selected_columns_layout = 1;
464 localcmd(sprintf("scoreboard_columns_set default\n"));
465 scoreboard_selected_columns_layout = 2;
468 localcmd(sprintf("scoreboard_columns_set all\n"));
469 scoreboard_selected_columns_layout = 0;
474 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
478 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
480 if (scoreboard_selected_player)
482 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
483 HUD_Scoreboard_UI_Disable();
487 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
491 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
493 if (scoreboard_selected_player)
494 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
497 else if(hit_con_bind || nPrimary == K_PAUSE)
503 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
504 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
506 #define SB_EXTRA_SORTING_FIELDS 5
507 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
508 void Scoreboard_InitScores()
512 ps_primary = ps_secondary = NULL;
513 ts_primary = ts_secondary = -1;
514 FOREACH(Scores, true, {
515 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
516 if(f == SFL_SORT_PRIO_PRIMARY)
518 if(f == SFL_SORT_PRIO_SECONDARY)
520 if(ps_primary == it || ps_secondary == it)
522 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
523 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
524 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
525 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
526 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
528 if(ps_secondary == NULL)
529 ps_secondary = ps_primary;
531 for(i = 0; i < MAX_TEAMSCORE; ++i)
533 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
534 if(f == SFL_SORT_PRIO_PRIMARY)
536 if(f == SFL_SORT_PRIO_SECONDARY)
539 if(ts_secondary == -1)
540 ts_secondary = ts_primary;
542 Cmd_Scoreboard_SetFields(0);
546 void Scoreboard_UpdatePlayerTeams()
548 static float update_time;
549 if (time <= update_time)
555 for(pl = players.sort_next; pl; pl = pl.sort_next)
558 int Team = entcs_GetScoreTeam(pl.sv_entnum);
559 if(SetTeam(pl, Team))
562 Scoreboard_UpdatePlayerPos(pl);
566 pl = players.sort_next;
571 print(strcat("PNUM: ", ftos(num), "\n"));
576 int Scoreboard_CompareScore(int vl, int vr, int f)
578 TC(int, vl); TC(int, vr); TC(int, f);
579 if(f & SFL_ZERO_IS_WORST)
581 if(vl == 0 && vr != 0)
583 if(vl != 0 && vr == 0)
587 return IS_INCREASING(f);
589 return IS_DECREASING(f);
593 float Scoreboard_ComparePlayerScores(entity left, entity right)
595 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
596 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
603 if(vl == NUM_SPECTATOR)
605 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
607 if(!left.gotscores && right.gotscores)
614 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
618 if (!fld) fld = ps_primary;
619 else if (ps_secondary == ps_primary) continue;
620 else fld = ps_secondary;
624 fld = sb_extra_sorting_field[i];
625 if (fld == ps_primary || fld == ps_secondary) continue;
629 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
630 if (r >= 0) return r;
633 if (left.sv_entnum < right.sv_entnum)
639 void Scoreboard_UpdatePlayerPos(entity player)
642 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
644 SORT_SWAP(player, ent);
646 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
648 SORT_SWAP(ent, player);
652 float Scoreboard_CompareTeamScores(entity left, entity right)
654 if(left.team == NUM_SPECTATOR)
656 if(right.team == NUM_SPECTATOR)
661 for(int i = -2; i < MAX_TEAMSCORE; ++i)
665 if (fld_idx == -1) fld_idx = ts_primary;
666 else if (ts_secondary == ts_primary) continue;
667 else fld_idx = ts_secondary;
672 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
675 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
676 if (r >= 0) return r;
679 if (left.team < right.team)
685 void Scoreboard_UpdateTeamPos(entity Team)
688 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
690 SORT_SWAP(Team, ent);
692 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
694 SORT_SWAP(ent, Team);
698 void Cmd_Scoreboard_Help()
700 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
701 LOG_HELP(_("Usage:"));
702 LOG_HELP("^2scoreboard_columns_set ^3default");
703 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
704 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
705 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
706 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
707 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
708 LOG_HELP(_("The following field names are recognized (case insensitive):"));
714 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
715 "of game types, then a slash, to make the field show up only in these\n"
716 "or in all but these game types. You can also specify 'all' as a\n"
717 "field to show all fields available for the current game mode."));
720 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
721 "include/exclude ALL teams/noteams game modes."));
724 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
725 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
726 "right of the vertical bar aligned to the right."));
727 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
728 "other gamemodes except DM."));
731 // NOTE: adding a gametype with ? to not warn for an optional field
732 // make sure it's excluded in a previous exclusive rule, if any
733 // otherwise the previous exclusive rule warns anyway
734 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
735 #define SCOREBOARD_DEFAULT_COLUMNS \
736 "ping pl fps name |" \
737 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
738 " -teams,lms/deaths +ft,tdm/deaths" \
740 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
741 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
742 " +tdm,ft,dom,ons,as/teamkills"\
743 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
744 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
745 " +lms/lives +lms/rank" \
746 " +kh/kckills +kh/losses +kh/caps" \
747 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
748 " +as/objectives +nb/faults +nb/goals" \
749 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
750 " +dom/ticks +dom/takes" \
751 " -lms,rc,cts,inv,nb/score"
753 void Cmd_Scoreboard_SetFields(int argc)
758 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
762 return; // do nothing, we don't know gametype and scores yet
764 // sbt_fields uses strunzone on the titles!
765 if(!sbt_field_title[0])
766 for(i = 0; i < MAX_SBT_FIELDS; ++i)
767 sbt_field_title[i] = strzone("(null)");
769 // TODO: re enable with gametype dependant cvars?
770 if(argc < 3) // no arguments provided
771 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
774 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
778 if(argv(2) == "default" || argv(2) == "expand_default")
780 if(argv(2) == "expand_default")
781 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
782 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
784 else if(argv(2) == "all" || argv(2) == "ALL")
786 string s = "ping pl name |"; // scores without label (not really scores)
789 // scores without label
790 s = strcat(s, " ", "sum");
791 s = strcat(s, " ", "kdratio");
792 s = strcat(s, " ", "frags");
794 FOREACH(Scores, true, {
796 if(it != ps_secondary)
797 if(scores_label(it) != "")
798 s = strcat(s, " ", scores_label(it));
800 if(ps_secondary != ps_primary)
801 s = strcat(s, " ", scores_label(ps_secondary));
802 s = strcat(s, " ", scores_label(ps_primary));
803 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
810 hud_fontsize = HUD_GetFontsize("hud_fontsize");
812 for(i = 1; i < argc - 1; ++i)
815 bool nocomplain = false;
816 if(substring(str, 0, 1) == "?")
819 str = substring(str, 1, strlen(str) - 1);
822 slash = strstrofs(str, "/", 0);
825 pattern = substring(str, 0, slash);
826 str = substring(str, slash + 1, strlen(str) - (slash + 1));
828 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
832 str = strtolower(str);
833 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
834 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
839 // fields without a label (not networked via the score system)
840 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
841 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
842 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
843 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
844 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
845 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
846 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
847 default: // fields with a label
849 // map alternative labels
850 if (str == "damage") str = "dmg";
851 if (str == "damagetaken") str = "dmgtaken";
853 FOREACH(Scores, true, {
854 if (str == strtolower(scores_label(it))) {
856 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
860 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
861 if(!nocomplain && str != "fps") // server can disable the fps field
862 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
864 strfree(sbt_field_title[sbt_num_fields]);
865 sbt_field_size[sbt_num_fields] = 0;
869 sbt_field[sbt_num_fields] = j;
872 if(j == ps_secondary)
873 have_secondary = true;
878 if(sbt_num_fields >= MAX_SBT_FIELDS)
882 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
884 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
885 have_secondary = true;
886 if(ps_primary == ps_secondary)
887 have_secondary = true;
888 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
890 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
894 strfree(sbt_field_title[sbt_num_fields]);
895 for(i = sbt_num_fields; i > 0; --i)
897 sbt_field_title[i] = sbt_field_title[i-1];
898 sbt_field_size[i] = sbt_field_size[i-1];
899 sbt_field[i] = sbt_field[i-1];
901 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
902 sbt_field[0] = SP_NAME;
904 LOG_INFO("fixed missing field 'name'");
908 strfree(sbt_field_title[sbt_num_fields]);
909 for(i = sbt_num_fields; i > 1; --i)
911 sbt_field_title[i] = sbt_field_title[i-1];
912 sbt_field_size[i] = sbt_field_size[i-1];
913 sbt_field[i] = sbt_field[i-1];
915 sbt_field_title[1] = strzone("|");
916 sbt_field[1] = SP_SEPARATOR;
917 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
919 LOG_INFO("fixed missing field '|'");
922 else if(!have_separator)
924 strcpy(sbt_field_title[sbt_num_fields], "|");
925 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
926 sbt_field[sbt_num_fields] = SP_SEPARATOR;
928 LOG_INFO("fixed missing field '|'");
932 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
933 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
934 sbt_field[sbt_num_fields] = ps_secondary;
936 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
940 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
941 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
942 sbt_field[sbt_num_fields] = ps_primary;
944 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
948 sbt_field[sbt_num_fields] = SP_END;
951 string Scoreboard_AddPlayerId(string pl_name, entity pl)
953 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
954 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
955 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
959 vector sbt_field_rgb;
960 string sbt_field_icon0;
961 string sbt_field_icon1;
962 string sbt_field_icon2;
963 vector sbt_field_icon0_rgb;
964 vector sbt_field_icon1_rgb;
965 vector sbt_field_icon2_rgb;
966 string Scoreboard_GetName(entity pl)
968 if(ready_waiting && pl.ready)
970 sbt_field_icon0 = "gfx/scoreboard/player_ready";
974 int f = entcs_GetClientColors(pl.sv_entnum);
976 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
977 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
978 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
979 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
980 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
983 return entcs_GetName(pl.sv_entnum);
986 string Scoreboard_GetField(entity pl, PlayerScoreField field)
988 float tmp, num, denom;
991 sbt_field_rgb = '1 1 1';
992 sbt_field_icon0 = "";
993 sbt_field_icon1 = "";
994 sbt_field_icon2 = "";
995 sbt_field_icon0_rgb = '1 1 1';
996 sbt_field_icon1_rgb = '1 1 1';
997 sbt_field_icon2_rgb = '1 1 1';
1002 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1003 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1007 tmp = max(0, min(220, f-80)) / 220;
1008 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1014 f = pl.ping_packetloss;
1015 tmp = pl.ping_movementloss;
1016 if(f == 0 && tmp == 0)
1018 str = ftos(ceil(f * 100));
1020 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1021 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1022 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1026 str = Scoreboard_GetName(pl);
1027 if (autocvar_hud_panel_scoreboard_playerid)
1028 str = Scoreboard_AddPlayerId(str, pl);
1032 f = pl.(scores(SP_KILLS));
1033 f -= pl.(scores(SP_SUICIDES));
1037 num = pl.(scores(SP_KILLS));
1038 denom = pl.(scores(SP_DEATHS));
1041 sbt_field_rgb = '0 1 0';
1042 str = sprintf("%d", num);
1043 } else if(num <= 0) {
1044 sbt_field_rgb = '1 0 0';
1045 str = sprintf("%.1f", num/denom);
1047 str = sprintf("%.1f", num/denom);
1051 f = pl.(scores(SP_KILLS));
1052 f -= pl.(scores(SP_DEATHS));
1055 sbt_field_rgb = '0 1 0';
1057 sbt_field_rgb = '1 1 1';
1059 sbt_field_rgb = '1 0 0';
1065 float elo = pl.(scores(SP_ELO));
1067 case -1: return "...";
1068 case -2: return _("N/A");
1069 default: return ftos(elo);
1075 float fps = pl.(scores(SP_FPS));
1078 sbt_field_rgb = '1 1 1';
1079 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1081 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1082 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1086 case SP_DMG: case SP_DMGTAKEN:
1087 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1089 default: case SP_SCORE:
1090 tmp = pl.(scores(field));
1091 f = scores_flags(field);
1092 if(field == ps_primary)
1093 sbt_field_rgb = '1 1 0';
1094 else if(field == ps_secondary)
1095 sbt_field_rgb = '0 1 1';
1097 sbt_field_rgb = '1 1 1';
1098 return ScoreString(f, tmp);
1103 float sbt_fixcolumnwidth_len;
1104 float sbt_fixcolumnwidth_iconlen;
1105 float sbt_fixcolumnwidth_marginlen;
1107 string Scoreboard_FixColumnWidth(int i, string str)
1113 sbt_fixcolumnwidth_iconlen = 0;
1115 if(sbt_field_icon0 != "")
1117 sz = draw_getimagesize(sbt_field_icon0);
1119 if(sbt_fixcolumnwidth_iconlen < f)
1120 sbt_fixcolumnwidth_iconlen = f;
1123 if(sbt_field_icon1 != "")
1125 sz = draw_getimagesize(sbt_field_icon1);
1127 if(sbt_fixcolumnwidth_iconlen < f)
1128 sbt_fixcolumnwidth_iconlen = f;
1131 if(sbt_field_icon2 != "")
1133 sz = draw_getimagesize(sbt_field_icon2);
1135 if(sbt_fixcolumnwidth_iconlen < f)
1136 sbt_fixcolumnwidth_iconlen = f;
1139 if(sbt_fixcolumnwidth_iconlen != 0)
1141 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1142 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1145 sbt_fixcolumnwidth_marginlen = 0;
1147 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1150 float remaining_space = 0;
1151 for(j = 0; j < sbt_num_fields; ++j)
1153 if (sbt_field[i] != SP_SEPARATOR)
1154 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1155 sbt_field_size[i] = panel_size.x - remaining_space;
1157 if (sbt_fixcolumnwidth_iconlen != 0)
1158 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1159 float namesize = panel_size.x - remaining_space;
1160 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1161 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1163 max_namesize = vid_conwidth - remaining_space;
1166 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1168 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1169 if(sbt_field_size[i] < f)
1170 sbt_field_size[i] = f;
1175 void Scoreboard_initFieldSizes()
1177 for(int i = 0; i < sbt_num_fields; ++i)
1179 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1180 Scoreboard_FixColumnWidth(i, "");
1184 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1187 vector column_dim = eY * panel_size.y;
1189 column_dim.y -= 1.25 * hud_fontsize.y;
1190 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1191 pos.x += hud_fontsize.x * 0.5;
1192 for(i = 0; i < sbt_num_fields; ++i)
1194 if(sbt_field[i] == SP_SEPARATOR)
1196 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1199 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1200 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1201 pos.x += column_dim.x;
1203 if(sbt_field[i] == SP_SEPARATOR)
1205 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1206 for(i = sbt_num_fields - 1; i > 0; --i)
1208 if(sbt_field[i] == SP_SEPARATOR)
1211 pos.x -= sbt_field_size[i];
1216 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1217 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1220 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1221 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1222 pos.x -= hud_fontsize.x;
1226 pos.x = panel_pos.x;
1227 pos.y += 1.25 * hud_fontsize.y;
1231 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1233 TC(bool, is_self); TC(int, pl_number);
1235 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1237 vector h_pos = item_pos;
1238 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1239 // alternated rows highlighting
1240 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1242 if (pl == scoreboard_selected_player)
1243 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1246 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1247 else if((sbt_highlight) && (!(pl_number % 2)))
1248 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1250 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1252 vector pos = item_pos;
1253 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1255 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1257 pos.x += hud_fontsize.x * 0.5;
1258 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1259 vector tmp = '0 0 0';
1261 PlayerScoreField field;
1262 for(i = 0; i < sbt_num_fields; ++i)
1264 field = sbt_field[i];
1265 if(field == SP_SEPARATOR)
1268 if(is_spec && field != SP_NAME && field != SP_PING) {
1269 pos.x += sbt_field_size[i] + hud_fontsize.x;
1272 str = Scoreboard_GetField(pl, field);
1273 str = Scoreboard_FixColumnWidth(i, str);
1275 pos.x += sbt_field_size[i] + hud_fontsize.x;
1277 if(field == SP_NAME) {
1278 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1279 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1281 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1282 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1285 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1286 if(sbt_field_icon0 != "")
1287 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1288 if(sbt_field_icon1 != "")
1289 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1290 if(sbt_field_icon2 != "")
1291 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1294 if(sbt_field[i] == SP_SEPARATOR)
1296 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1297 for(i = sbt_num_fields-1; i > 0; --i)
1299 field = sbt_field[i];
1300 if(field == SP_SEPARATOR)
1303 if(is_spec && field != SP_NAME && field != SP_PING) {
1304 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1308 str = Scoreboard_GetField(pl, field);
1309 str = Scoreboard_FixColumnWidth(i, str);
1311 if(field == SP_NAME) {
1312 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1313 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1315 tmp.x = sbt_fixcolumnwidth_len;
1316 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1319 tmp.x = sbt_field_size[i];
1320 if(sbt_field_icon0 != "")
1321 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1322 if(sbt_field_icon1 != "")
1323 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1324 if(sbt_field_icon2 != "")
1325 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1326 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1331 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1334 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1337 vector h_pos = item_pos;
1338 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1340 bool complete = (this_team == NUM_SPECTATOR);
1343 if((sbt_highlight) && (!(pl_number % 2)))
1344 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1346 vector pos = item_pos;
1347 pos.x += hud_fontsize.x * 0.5;
1348 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1350 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1352 width_limit -= stringwidth("...", false, hud_fontsize);
1353 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1354 static float max_name_width = 0;
1356 float fieldsize = 0;
1357 float min_fieldsize = 0;
1358 float fieldpadding = hud_fontsize.x * 0.25;
1359 if(this_team == NUM_SPECTATOR)
1361 if(autocvar_hud_panel_scoreboard_spectators_showping)
1362 min_fieldsize = stringwidth("999", false, hud_fontsize);
1364 else if(autocvar_hud_panel_scoreboard_others_showscore)
1365 min_fieldsize = stringwidth("99", false, hud_fontsize);
1366 for(i = 0; pl; pl = pl.sort_next)
1368 if(pl.team != this_team)
1370 if(pl == ignored_pl)
1374 if(this_team == NUM_SPECTATOR)
1376 if(autocvar_hud_panel_scoreboard_spectators_showping)
1377 field = Scoreboard_GetField(pl, SP_PING);
1379 else if(autocvar_hud_panel_scoreboard_others_showscore)
1380 field = Scoreboard_GetField(pl, SP_SCORE);
1382 string str = entcs_GetName(pl.sv_entnum);
1383 if (autocvar_hud_panel_scoreboard_playerid)
1384 str = Scoreboard_AddPlayerId(str, pl);
1385 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1386 float column_width = stringwidth(str, true, hud_fontsize);
1387 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1389 if(column_width > max_name_width)
1390 max_name_width = column_width;
1391 column_width = max_name_width;
1395 fieldsize = stringwidth(field, false, hud_fontsize);
1396 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1399 if(pos.x + column_width > width_limit)
1404 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1409 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1410 pos.y += hud_fontsize.y * 1.25;
1414 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1416 if (pl == scoreboard_selected_player)
1418 h_size.x = column_width + hud_fontsize.x * 0.25;
1419 h_size.y = hud_fontsize.y;
1420 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1424 vector name_pos = pos;
1425 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1426 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1427 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1430 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1431 h_size.y = hud_fontsize.y;
1432 vector field_pos = pos;
1433 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1434 field_pos.x += column_width - h_size.x;
1436 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1437 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1438 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1442 h_size.x = column_width + hud_fontsize.x * 0.25;
1443 h_size.y = hud_fontsize.y;
1444 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1446 pos.x += column_width;
1447 pos.x += hud_fontsize.x;
1449 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1452 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1454 int max_players = 999;
1455 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1457 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1460 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1461 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1462 height /= team_count;
1465 height -= panel_bg_padding * 2; // - padding
1466 max_players = floor(height / (hud_fontsize.y * 1.25));
1467 if(max_players <= 1)
1469 if(max_players == tm.team_size)
1474 entity me = playerslots[current_player];
1476 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1477 panel_size.y += panel_bg_padding * 2;
1479 vector scoreboard_selected_hl_pos = pos;
1480 vector scoreboard_selected_hl_size = '0 0 0';
1481 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1482 scoreboard_selected_hl_size.y = panel_size.y;
1486 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1487 if(panel.current_panel_bg != "0")
1488 end_pos.y += panel_bg_border * 2;
1490 if(panel_bg_padding)
1492 panel_pos += '1 1 0' * panel_bg_padding;
1493 panel_size -= '2 2 0' * panel_bg_padding;
1497 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1501 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1503 pos.y += 1.25 * hud_fontsize.y;
1506 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1508 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1511 // print header row and highlight columns
1512 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1514 // fill the table and draw the rows
1515 bool is_self = false;
1516 bool self_shown = false;
1518 for(pl = players.sort_next; pl; pl = pl.sort_next)
1520 if(pl.team != tm.team)
1522 if(i == max_players - 2 && pl != me)
1524 if(!self_shown && me.team == tm.team)
1526 Scoreboard_DrawItem(pos, rgb, me, true, i);
1528 pos.y += 1.25 * hud_fontsize.y;
1532 if(i >= max_players - 1)
1534 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1537 is_self = (pl.sv_entnum == current_player);
1538 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1541 pos.y += 1.25 * hud_fontsize.y;
1545 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1547 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1549 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1550 _alpha *= panel_fg_alpha;
1552 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1556 panel_size.x += panel_bg_padding * 2; // restore initial width
1560 bool Scoreboard_WouldDraw()
1562 if (scoreboard_ui_enabled)
1564 if (scoreboard_ui_disabling)
1566 if (scoreboard_fade_alpha == 0)
1567 HUD_Scoreboard_UI_Disable_Instantly();
1570 if (intermission && scoreboard_ui_enabled == 2)
1572 HUD_Scoreboard_UI_Disable_Instantly();
1577 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1579 else if (QuickMenu_IsOpened())
1581 else if (HUD_Radar_Clickable())
1583 else if (scoreboard_showscores)
1585 else if (intermission == 1)
1587 else if (intermission == 2)
1589 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1590 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1594 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1599 float average_accuracy;
1600 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1602 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1604 WepSet weapons_stat = WepSet_GetFromStat();
1605 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1606 int disownedcnt = 0;
1608 FOREACH(Weapons, it != WEP_Null, {
1609 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1611 WepSet set = it.m_wepset;
1612 if(it.spawnflags & WEP_TYPE_OTHER)
1617 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1619 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1626 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1627 if (weapon_cnt <= 0) return pos;
1630 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1632 int columns = ceil(weapon_cnt / rows);
1634 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1635 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1636 float height = weapon_height + hud_fontsize.y;
1638 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);
1639 pos.y += 1.25 * hud_fontsize.y;
1640 if(panel.current_panel_bg != "0")
1641 pos.y += panel_bg_border;
1644 panel_size.y = height * rows;
1645 panel_size.y += panel_bg_padding * 2;
1647 float panel_bg_alpha_save = panel_bg_alpha;
1648 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1650 panel_bg_alpha = panel_bg_alpha_save;
1652 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1653 if(panel.current_panel_bg != "0")
1654 end_pos.y += panel_bg_border * 2;
1656 if(panel_bg_padding)
1658 panel_pos += '1 1 0' * panel_bg_padding;
1659 panel_size -= '2 2 0' * panel_bg_padding;
1663 vector tmp = panel_size;
1665 float weapon_width = tmp.x / columns / rows;
1668 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1672 // column highlighting
1673 for (int i = 0; i < columns; ++i)
1675 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);
1678 for (int i = 0; i < rows; ++i)
1679 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1682 average_accuracy = 0;
1683 int weapons_with_stats = 0;
1685 pos.x += weapon_width / 2;
1687 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1690 Accuracy_LoadColors();
1692 float oldposx = pos.x;
1696 FOREACH(Weapons, it != WEP_Null, {
1697 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1699 WepSet set = it.m_wepset;
1700 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1702 if (it.spawnflags & WEP_TYPE_OTHER)
1706 if (weapon_stats >= 0)
1707 weapon_alpha = sbt_fg_alpha;
1709 weapon_alpha = 0.2 * sbt_fg_alpha;
1712 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1714 if (weapon_stats >= 0) {
1715 weapons_with_stats += 1;
1716 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1718 string s = sprintf("%d%%", weapon_stats * 100);
1719 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1721 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1722 rgb = Accuracy_GetColor(weapon_stats);
1724 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1726 tmpos.x += weapon_width * rows;
1727 pos.x += weapon_width * rows;
1728 if (rows == 2 && column == columns - 1) {
1736 if (weapons_with_stats)
1737 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1739 panel_size.x += panel_bg_padding * 2; // restore initial width
1744 bool is_item_filtered(entity it)
1746 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1748 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1751 if (it.instanceOfArmor || it.instanceOfHealth)
1753 int ha_mask = floor(mask) % 10;
1756 default: return false;
1757 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1758 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1759 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1760 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1763 if (it.instanceOfAmmo)
1765 int ammo_mask = floor(mask / 10) % 10;
1766 return (ammo_mask == 1);
1771 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1773 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1775 int disowned_cnt = 0;
1776 int uninteresting_cnt = 0;
1777 IL_EACH(default_order_items, true, {
1778 int q = g_inventory.inv_items[it.m_id];
1779 //q = 1; // debug: display all items
1780 if (is_item_filtered(it))
1781 ++uninteresting_cnt;
1785 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1786 int n = items_cnt - disowned_cnt;
1787 if (n <= 0) return pos;
1789 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1790 int columns = max(6, ceil(n / rows));
1792 float item_height = hud_fontsize.y * 2.3;
1793 float height = item_height + hud_fontsize.y;
1795 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1796 pos.y += 1.25 * hud_fontsize.y;
1797 if(panel.current_panel_bg != "0")
1798 pos.y += panel_bg_border;
1801 panel_size.y = height * rows;
1802 panel_size.y += panel_bg_padding * 2;
1804 float panel_bg_alpha_save = panel_bg_alpha;
1805 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1807 panel_bg_alpha = panel_bg_alpha_save;
1809 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1810 if(panel.current_panel_bg != "0")
1811 end_pos.y += panel_bg_border * 2;
1813 if(panel_bg_padding)
1815 panel_pos += '1 1 0' * panel_bg_padding;
1816 panel_size -= '2 2 0' * panel_bg_padding;
1820 vector tmp = panel_size;
1822 float item_width = tmp.x / columns / rows;
1825 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1829 // column highlighting
1830 for (int i = 0; i < columns; ++i)
1832 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);
1835 for (int i = 0; i < rows; ++i)
1836 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1840 pos.x += item_width / 2;
1842 float oldposx = pos.x;
1846 IL_EACH(default_order_items, !is_item_filtered(it), {
1847 int n = g_inventory.inv_items[it.m_id];
1848 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1849 if (n <= 0) continue;
1850 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);
1852 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1853 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1854 tmpos.x += item_width * rows;
1855 pos.x += item_width * rows;
1856 if (rows == 2 && column == columns - 1) {
1864 panel_size.x += panel_bg_padding * 2; // restore initial width
1869 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1871 pos.x += hud_fontsize.x * 0.25;
1872 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1873 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1874 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1876 pos.y += hud_fontsize.y;
1881 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1882 float stat_secrets_found, stat_secrets_total;
1883 float stat_monsters_killed, stat_monsters_total;
1887 // get monster stats
1888 stat_monsters_killed = STAT(MONSTERS_KILLED);
1889 stat_monsters_total = STAT(MONSTERS_TOTAL);
1891 // get secrets stats
1892 stat_secrets_found = STAT(SECRETS_FOUND);
1893 stat_secrets_total = STAT(SECRETS_TOTAL);
1895 // get number of rows
1896 if(stat_secrets_total)
1898 if(stat_monsters_total)
1901 // if no rows, return
1905 // draw table header
1906 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1907 pos.y += 1.25 * hud_fontsize.y;
1908 if(panel.current_panel_bg != "0")
1909 pos.y += panel_bg_border;
1912 panel_size.y = hud_fontsize.y * rows;
1913 panel_size.y += panel_bg_padding * 2;
1916 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1917 if(panel.current_panel_bg != "0")
1918 end_pos.y += panel_bg_border * 2;
1920 if(panel_bg_padding)
1922 panel_pos += '1 1 0' * panel_bg_padding;
1923 panel_size -= '2 2 0' * panel_bg_padding;
1927 vector tmp = panel_size;
1930 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1933 if(stat_monsters_total)
1935 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1936 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1940 if(stat_secrets_total)
1942 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1943 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1946 panel_size.x += panel_bg_padding * 2; // restore initial width
1950 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1953 RANKINGS_RECEIVED_CNT = 0;
1954 for (i=RANKINGS_CNT-1; i>=0; --i)
1956 ++RANKINGS_RECEIVED_CNT;
1958 if (RANKINGS_RECEIVED_CNT == 0)
1961 vector hl_rgb = rgb + '0.5 0.5 0.5';
1963 vector scoreboard_selected_hl_pos = pos;
1965 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1966 pos.y += 1.25 * hud_fontsize.y;
1967 if(panel.current_panel_bg != "0")
1968 pos.y += panel_bg_border;
1970 vector scoreboard_selected_hl_size = '0 0 0';
1971 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1972 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1977 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1979 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1984 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1986 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1990 float ranksize = 3 * hud_fontsize.x;
1991 float timesize = 5 * hud_fontsize.x;
1992 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1993 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1994 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1997 rankings_cnt = RANKINGS_RECEIVED_CNT;
1998 rankings_rows = ceil(rankings_cnt / rankings_columns);
2001 // expand name column to fill the entire row
2002 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2003 namesize += available_space;
2004 columnsize.x += available_space;
2006 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2007 panel_size.y += panel_bg_padding * 2;
2008 scoreboard_selected_hl_size.y += panel_size.y;
2012 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2013 if(panel.current_panel_bg != "0")
2014 end_pos.y += panel_bg_border * 2;
2016 if(panel_bg_padding)
2018 panel_pos += '1 1 0' * panel_bg_padding;
2019 panel_size -= '2 2 0' * panel_bg_padding;
2025 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2027 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2029 int column = 0, j = 0;
2030 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2031 int start_item = rankings_start_column * rankings_rows;
2032 for(i = start_item; i < start_item + rankings_cnt; ++i)
2034 int t = grecordtime[i];
2038 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2039 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2040 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2041 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2043 str = count_ordinal(i+1);
2044 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2045 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2046 str = ColorTranslateRGB(grecordholder[i]);
2048 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2049 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2051 pos.y += 1.25 * hud_fontsize.y;
2053 if(j >= rankings_rows)
2057 pos.x += panel_size.x / rankings_columns;
2058 pos.y = panel_pos.y;
2061 strfree(zoned_name_self);
2063 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2065 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2066 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2069 panel_size.x += panel_bg_padding * 2; // restore initial width
2073 bool have_weapon_stats;
2074 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2076 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2078 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2081 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2082 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2088 if (!have_weapon_stats)
2090 FOREACH(Weapons, it != WEP_Null, {
2091 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2092 if (weapon_stats >= 0)
2094 have_weapon_stats = true;
2098 if (!have_weapon_stats)
2105 bool have_item_stats;
2106 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2108 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2110 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2113 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2114 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2120 if (!have_item_stats)
2122 IL_EACH(default_order_items, true, {
2123 if (!is_item_filtered(it))
2125 int q = g_inventory.inv_items[it.m_id];
2126 //q = 1; // debug: display all items
2129 have_item_stats = true;
2134 if (!have_item_stats)
2141 vector Scoreboard_Spectators_Draw(vector pos) {
2146 for(pl = players.sort_next; pl; pl = pl.sort_next)
2148 if(pl.team == NUM_SPECTATOR)
2150 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2151 if(tm.team == NUM_SPECTATOR)
2153 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2154 draw_beginBoldFont();
2155 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2157 pos.y += 1.25 * hud_fontsize.y;
2159 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2160 pos.y += 1.25 * hud_fontsize.y;
2165 if (str != "") // if there's at least one spectator
2166 pos.y += 0.5 * hud_fontsize.y;
2171 void Scoreboard_Draw()
2173 if(!autocvar__hud_configure)
2175 if(!hud_draw_maximized) return;
2177 // frametime checks allow to toggle the scoreboard even when the game is paused
2178 if(scoreboard_active) {
2179 if (scoreboard_fade_alpha == 0)
2180 scoreboard_time = time;
2181 if(hud_configure_menu_open == 1)
2182 scoreboard_fade_alpha = 1;
2183 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2184 if (scoreboard_fadeinspeed && frametime)
2185 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2187 scoreboard_fade_alpha = 1;
2188 if(hud_fontsize_str != autocvar_hud_fontsize)
2190 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2191 Scoreboard_initFieldSizes();
2192 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2196 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2197 if (scoreboard_fadeoutspeed && frametime)
2198 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2200 scoreboard_fade_alpha = 0;
2203 if (!scoreboard_fade_alpha)
2205 scoreboard_acc_fade_alpha = 0;
2206 scoreboard_itemstats_fade_alpha = 0;
2211 scoreboard_fade_alpha = 0;
2213 if (autocvar_hud_panel_scoreboard_dynamichud)
2216 HUD_Scale_Disable();
2218 if(scoreboard_fade_alpha <= 0)
2220 panel_fade_alpha *= scoreboard_fade_alpha;
2221 HUD_Panel_LoadCvars();
2223 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2224 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2225 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2226 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2227 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2228 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2229 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2231 // don't overlap with con_notify
2232 if(!autocvar__hud_configure)
2233 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2235 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2236 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2237 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2238 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2239 panel_pos.x = scoreboard_left;
2240 panel_size.x = fixed_scoreboard_width;
2242 Scoreboard_UpdatePlayerTeams();
2244 scoreboard_top = panel_pos.y;
2245 vector pos = panel_pos;
2250 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2252 // Begin of Game Info Section
2253 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2254 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2256 // Game Info: Game Type
2257 if (scoreboard_ui_enabled == 2)
2258 str = _("Team Selection");
2260 str = MapInfo_Type_ToText(gametype);
2261 draw_beginBoldFont();
2262 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);
2265 pos.y += sb_gameinfo_type_fontsize.y;
2266 // Game Info: Game Detail
2267 if (scoreboard_ui_enabled == 2)
2269 if (scoreboard_selected_team)
2270 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2272 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2273 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2275 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2276 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2277 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);
2281 float tl = STAT(TIMELIMIT);
2282 float fl = STAT(FRAGLIMIT);
2283 float ll = STAT(LEADLIMIT);
2284 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2287 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2288 if(!gametype.m_hidelimits)
2293 str = strcat(str, "^7 / "); // delimiter
2296 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2297 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2298 (teamscores_label(ts_primary) == "fastest") ? "" :
2299 TranslateScoresLabel(teamscores_label(ts_primary))));
2303 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2304 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2305 (scores_label(ps_primary) == "fastest") ? "" :
2306 TranslateScoresLabel(scores_label(ps_primary))));
2311 if(tl > 0 || fl > 0)
2314 if (ll_and_fl && fl > 0)
2315 str = strcat(str, "^7 & ");
2317 str = strcat(str, "^7 / ");
2322 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2323 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2324 (teamscores_label(ts_primary) == "fastest") ? "" :
2325 TranslateScoresLabel(teamscores_label(ts_primary))));
2329 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2330 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2331 (scores_label(ps_primary) == "fastest") ? "" :
2332 TranslateScoresLabel(scores_label(ps_primary))));
2336 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
2338 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2339 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2341 // End of Game Info Section
2343 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2344 if(panel.current_panel_bg != "0")
2345 pos.y += panel_bg_border;
2347 // Draw the scoreboard
2348 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2351 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2355 vector panel_bg_color_save = panel_bg_color;
2356 vector team_score_baseoffset;
2357 vector team_size_baseoffset;
2358 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2360 // put team score to the left of scoreboard (and team size to the right)
2361 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2362 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2363 if(panel.current_panel_bg != "0")
2365 team_score_baseoffset.x -= panel_bg_border;
2366 team_size_baseoffset.x += panel_bg_border;
2371 // put team score to the right of scoreboard (and team size to the left)
2372 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2373 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2374 if(panel.current_panel_bg != "0")
2376 team_score_baseoffset.x += panel_bg_border;
2377 team_size_baseoffset.x -= panel_bg_border;
2381 int team_size_total = 0;
2382 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2384 // calculate team size total (sum of all team sizes)
2385 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2386 if(tm.team != NUM_SPECTATOR)
2387 team_size_total += tm.team_size;
2390 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2392 if(tm.team == NUM_SPECTATOR)
2397 draw_beginBoldFont();
2398 vector rgb = Team_ColorRGB(tm.team);
2399 str = ftos(tm.(teamscores(ts_primary)));
2400 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2402 // team score on the left (default)
2403 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2407 // team score on the right
2408 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2410 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2412 // team size (if set to show on the side)
2413 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2415 // calculate the starting position for the whole team size info string
2416 str = sprintf("%d/%d", tm.team_size, team_size_total);
2417 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2419 // team size on the left
2420 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2424 // team size on the right
2425 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2427 str = sprintf("%d", tm.team_size);
2428 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2429 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2430 str = sprintf("/%d", team_size_total);
2431 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2435 // secondary score, e.g. keyhunt
2436 if(ts_primary != ts_secondary)
2438 str = ftos(tm.(teamscores(ts_secondary)));
2439 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2442 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2447 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2450 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2453 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2454 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2455 else if(panel_bg_color_team > 0)
2456 panel_bg_color = rgb * panel_bg_color_team;
2458 panel_bg_color = rgb;
2459 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2461 panel_bg_color = panel_bg_color_save;
2465 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2466 if(tm.team != NUM_SPECTATOR)
2469 // display it anyway
2470 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2473 // draw scoreboard spectators before accuracy and item stats
2474 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2475 pos = Scoreboard_Spectators_Draw(pos);
2478 // draw accuracy and item stats
2479 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2480 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2481 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2482 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2484 // draw scoreboard spectators after accuracy and item stats and before rankings
2485 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2486 pos = Scoreboard_Spectators_Draw(pos);
2489 if(MUTATOR_CALLHOOK(ShowRankings)) {
2490 string ranktitle = M_ARGV(0, string);
2491 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2492 if(race_speedaward_alltimebest)
2495 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2499 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2500 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2501 str = strcat(str, " / ");
2503 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2504 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2505 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2506 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2508 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2513 // draw scoreboard spectators after rankings
2514 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2515 pos = Scoreboard_Spectators_Draw(pos);
2518 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2520 // draw scoreboard spectators after mapstats
2521 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2522 pos = Scoreboard_Spectators_Draw(pos);
2526 // print information about respawn status
2527 float respawn_time = STAT(RESPAWN_TIME);
2531 if(respawn_time < 0)
2533 // a negative number means we are awaiting respawn, time value is still the same
2534 respawn_time *= -1; // remove mark now that we checked it
2536 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2537 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2539 str = sprintf(_("^1Respawning in ^3%s^1..."),
2540 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2541 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2543 count_seconds(ceil(respawn_time - time))
2547 else if(time < respawn_time)
2549 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2550 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2551 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2553 count_seconds(ceil(respawn_time - time))
2557 else if(time >= respawn_time)
2558 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2560 pos.y += 1.2 * hud_fontsize.y;
2561 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2564 pos.y += hud_fontsize.y;
2565 if (scoreboard_fade_alpha < 1)
2566 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2567 else if (pos.y != scoreboard_bottom)
2569 if (pos.y > scoreboard_bottom)
2570 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2572 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2577 if (scoreboard_fade_alpha == 1)
2579 if (scoreboard_bottom > 0.95 * vid_conheight)
2580 rankings_rows = max(1, rankings_rows - 1);
2581 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2582 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2584 rankings_cnt = rankings_rows * rankings_columns;