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];
46 float sbt_field_title_condense_factor[MAX_SBT_FIELDS + 1];
47 float sbt_field_title_width[MAX_SBT_FIELDS + 1];
49 float sbt_field_title_maxwidth;
50 float sbt_field_title_maxwidth_factor;
52 string autocvar_hud_fontsize;
54 float name_field_index;
55 int sb_field_sizes_init;
59 float sbt_fg_alpha_self;
61 float sbt_highlight_alpha;
62 float sbt_highlight_alpha_self;
63 float sbt_highlight_alpha_eliminated;
65 // provide basic panel cvars to old clients
66 // TODO remove them after a future release (0.8.2+)
67 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
68 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
69 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
70 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
71 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
72 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
73 noref string autocvar_hud_panel_scoreboard_bg_border = "";
74 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
76 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
77 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
78 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
79 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
80 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
81 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
82 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
83 float autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth = 0.07;
84 bool autocvar_hud_panel_scoreboard_table_highlight = true;
85 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
86 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
87 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
88 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
89 float autocvar_hud_panel_scoreboard_team_size_position = 0;
90 float autocvar_hud_panel_scoreboard_spectators_position = 1;
92 bool autocvar_hud_panel_scoreboard_accuracy = true;
93 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
94 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
95 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
96 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
98 bool autocvar_hud_panel_scoreboard_itemstats = true;
99 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
100 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
101 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
102 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
103 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
105 bool autocvar_hud_panel_scoreboard_dynamichud = false;
107 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
108 bool autocvar_hud_panel_scoreboard_others_showscore = true;
109 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
110 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
111 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
112 bool autocvar_hud_panel_scoreboard_playerid = false;
113 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
114 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
115 bool autocvar_hud_panel_scoreboard_scores_per_round;
117 float scoreboard_time;
121 if(autocvar_hud_panel_scoreboard_scores_per_round)
122 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
125 // mode 0: returns translated label
126 // mode 1: prints name and description of all the labels
127 string Label_getInfo(string label, int mode)
130 label = "bckills"; // first case in the switch
132 #define SCO_LABEL(strlabel, label, padding, help) \
135 return CTX(strlabel); \
136 LOG_HELP("^3", label, padding, "^7", help);
140 SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
141 SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
142 SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
143 SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
144 SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
145 SCO_LABEL(_("SCO^destructions"), "destructions", " ", _("Number of keys destroyed by pushing them into void"));
146 SCO_LABEL(_("SCO^damage dealt"), "dmg", " ", _("The total damage dealt"));
147 SCO_LABEL(_("SCO^damage taken"), "dmgtaken", " ", _("The total damage taken"));
148 SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
149 SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
150 SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
151 SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
152 SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
153 SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
154 SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
155 SCO_LABEL(_("SCO^generators"), "generators", " ", _("Number of generators destroyed"));
156 SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
157 SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
158 SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
159 SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
160 SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
161 SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
162 SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
163 SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
164 SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
165 SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
166 SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
167 SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
168 SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
169 SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
170 SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
171 SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
172 SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
173 SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
174 SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
175 SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
176 SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
177 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
178 SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
179 SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
180 SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
181 SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
182 SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
183 SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
184 SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
185 SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
191 bool scoreboard_ui_disabling;
192 void HUD_Scoreboard_UI_Disable()
194 scoreboard_ui_disabling = true;
195 sb_showscores = false;
198 void HUD_Scoreboard_UI_Disable_Instantly()
200 scoreboard_ui_disabling = false;
201 scoreboard_ui_enabled = 0;
202 scoreboard_selected_panel = 0;
203 scoreboard_selected_player = NULL;
204 scoreboard_selected_team = NULL;
207 // mode: 0 normal, 1 team selection
208 void Scoreboard_UI_Enable(int mode)
214 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
217 // release player's pressed keys as they aren't released elsewhere
218 // in particular jump needs to be released as it may open the team selection
219 // (when server detects jump has been pressed it sends the command to open the team selection)
220 Release_Common_Keys();
221 scoreboard_ui_enabled = 2;
222 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
226 if (scoreboard_ui_enabled == 1)
228 scoreboard_ui_enabled = 1;
229 scoreboard_selected_panel = SB_PANEL_FIRST;
231 scoreboard_selected_player = NULL;
232 scoreboard_selected_team = NULL;
233 scoreboard_selected_panel_time = time;
236 int rankings_start_column;
237 int rankings_rows = 0;
238 int rankings_columns = 0;
239 int rankings_cnt = 0;
240 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
244 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
249 mousepos.x = nPrimary;
250 mousepos.y = nSecondary;
257 // at this point bInputType can only be 0 or 1 (key pressed or released)
258 bool key_pressed = (bInputType == 0);
260 // ESC to exit (TAB-ESC works too)
261 if(nPrimary == K_ESCAPE)
265 HUD_Scoreboard_UI_Disable();
269 // block any input while a menu dialog is fading
270 if(autocvar__menu_alpha)
276 // allow console bind to work
277 string con_keys = findkeysforcommand("toggleconsole", 0);
278 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
280 bool hit_con_bind = false;
282 for (i = 0; i < keys; ++i)
284 if(nPrimary == stof(argv(i)))
289 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
290 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
291 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
292 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
295 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
296 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
297 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
298 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
301 if(nPrimary == K_TAB)
305 if (scoreboard_ui_enabled == 2)
307 if (hudShiftState & S_SHIFT)
310 goto downarrow_action;
313 if (hudShiftState & S_SHIFT)
315 --scoreboard_selected_panel;
316 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
317 --scoreboard_selected_panel;
318 if (scoreboard_selected_panel < SB_PANEL_FIRST)
319 scoreboard_selected_panel = SB_PANEL_MAX;
323 ++scoreboard_selected_panel;
324 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
325 ++scoreboard_selected_panel;
326 if (scoreboard_selected_panel > SB_PANEL_MAX)
327 scoreboard_selected_panel = SB_PANEL_FIRST;
330 scoreboard_selected_panel_time = time;
332 else if(nPrimary == K_DOWNARROW)
336 LABEL(downarrow_action);
337 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
339 if (scoreboard_ui_enabled == 2)
341 entity curr_team = NULL;
342 bool scoreboard_selected_team_found = false;
343 if (!scoreboard_selected_team)
344 scoreboard_selected_team_found = true;
346 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
348 if(tm.team == NUM_SPECTATOR)
351 if (scoreboard_selected_team_found)
353 if (scoreboard_selected_team == tm)
354 scoreboard_selected_team_found = true;
357 if (curr_team == scoreboard_selected_team) // loop reached the last team
359 scoreboard_selected_team = curr_team;
364 entity curr_pl = NULL;
365 bool scoreboard_selected_player_found = false;
366 if (!scoreboard_selected_player)
367 scoreboard_selected_player_found = true;
369 for(tm = teams.sort_next; tm; tm = tm.sort_next)
371 if(tm.team != NUM_SPECTATOR)
372 for(pl = players.sort_next; pl; pl = pl.sort_next)
374 if(pl.team != tm.team)
377 if (scoreboard_selected_player_found)
379 if (scoreboard_selected_player == pl)
380 scoreboard_selected_player_found = true;
384 if (curr_pl == scoreboard_selected_player) // loop reached the last player
386 scoreboard_selected_player = curr_pl;
390 else if(nPrimary == K_UPARROW)
394 LABEL(uparrow_action);
395 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
397 if (scoreboard_ui_enabled == 2)
399 entity prev_team = NULL;
400 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
402 if(tm.team == NUM_SPECTATOR)
404 if (tm == scoreboard_selected_team)
409 scoreboard_selected_team = prev_team;
413 entity prev_pl = NULL;
415 for(tm = teams.sort_next; tm; tm = tm.sort_next)
417 if(tm.team != NUM_SPECTATOR)
418 for(pl = players.sort_next; pl; pl = pl.sort_next)
420 if(pl.team != tm.team)
422 if (pl == scoreboard_selected_player)
428 scoreboard_selected_player = prev_pl;
432 else if(nPrimary == K_RIGHTARROW)
436 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
437 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
439 else if(nPrimary == K_LEFTARROW)
443 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
444 rankings_start_column = max(rankings_start_column - 1, 0);
446 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
450 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
452 if (scoreboard_ui_enabled == 2)
455 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
458 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
459 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
460 HUD_Scoreboard_UI_Disable();
462 else if (scoreboard_selected_player)
463 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
466 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
470 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
472 switch (scoreboard_selected_columns_layout)
475 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
477 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
478 scoreboard_selected_columns_layout = 1;
483 localcmd(sprintf("scoreboard_columns_set default\n"));
484 scoreboard_selected_columns_layout = 2;
487 localcmd(sprintf("scoreboard_columns_set all\n"));
488 scoreboard_selected_columns_layout = 0;
493 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
497 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
498 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
500 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
504 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
506 if (scoreboard_selected_player)
508 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
509 HUD_Scoreboard_UI_Disable();
513 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
517 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
519 if (scoreboard_selected_player)
520 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
523 else if(hit_con_bind || nPrimary == K_PAUSE)
529 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
530 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
532 void Scoreboard_InitScores()
536 ps_primary = ps_secondary = NULL;
537 ts_primary = ts_secondary = -1;
538 FOREACH(Scores, true, {
539 if(scores_flags(it) & SFL_NOT_SORTABLE)
541 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
542 if(f == SFL_SORT_PRIO_PRIMARY)
544 if(f == SFL_SORT_PRIO_SECONDARY)
547 if(ps_secondary == NULL)
548 ps_secondary = ps_primary;
550 for(i = 0; i < MAX_TEAMSCORE; ++i)
552 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
553 if(f == SFL_SORT_PRIO_PRIMARY)
555 if(f == SFL_SORT_PRIO_SECONDARY)
558 if(ts_secondary == -1)
559 ts_secondary = ts_primary;
561 Cmd_Scoreboard_SetFields(0);
565 void Scoreboard_UpdatePlayerTeams()
567 static float update_time;
568 if (time <= update_time)
575 for(pl = players.sort_next; pl; pl = pl.sort_next)
577 numplayers += pl.team != NUM_SPECTATOR;
579 int Team = entcs_GetScoreTeam(pl.sv_entnum);
580 if(SetTeam(pl, Team))
583 Scoreboard_UpdatePlayerPos(pl);
587 pl = players.sort_next;
592 print(strcat("PNUM: ", ftos(num), "\n"));
597 int Scoreboard_CompareScore(int vl, int vr, int f)
599 TC(int, vl); TC(int, vr); TC(int, f);
600 if(f & SFL_ZERO_IS_WORST)
602 if(vl == 0 && vr != 0)
604 if(vl != 0 && vr == 0)
608 return IS_INCREASING(f);
610 return IS_DECREASING(f);
614 float Scoreboard_ComparePlayerScores(entity left, entity right)
616 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
617 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
624 if(vl == NUM_SPECTATOR)
626 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
628 if(!left.gotscores && right.gotscores)
633 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
634 if (res >= 0) return res;
636 if (ps_secondary && ps_secondary != ps_primary)
638 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
639 if (res >= 0) return res;
642 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
643 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
644 if (res >= 0) return res;
647 if (left.sv_entnum < right.sv_entnum)
653 void Scoreboard_UpdatePlayerPos(entity player)
656 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
658 SORT_SWAP(player, ent);
660 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
662 SORT_SWAP(ent, player);
666 float Scoreboard_CompareTeamScores(entity left, entity right)
668 if(left.team == NUM_SPECTATOR)
670 if(right.team == NUM_SPECTATOR)
675 for(int i = -2; i < MAX_TEAMSCORE; ++i)
679 if (fld_idx == -1) fld_idx = ts_primary;
680 else if (ts_secondary == ts_primary) continue;
681 else fld_idx = ts_secondary;
686 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
689 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
690 if (r >= 0) return r;
693 if (left.team < right.team)
699 void Scoreboard_UpdateTeamPos(entity Team)
702 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
704 SORT_SWAP(Team, ent);
706 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
708 SORT_SWAP(ent, Team);
712 void Cmd_Scoreboard_Help()
714 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
715 LOG_HELP(_("Usage:"));
716 LOG_HELP("^2scoreboard_columns_set ^3default");
717 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
718 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
719 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
720 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
721 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
722 LOG_HELP(_("The following field names are recognized (case insensitive):"));
728 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
729 "of game types, then a slash, to make the field show up only in these\n"
730 "or in all but these game types. You can also specify 'all' as a\n"
731 "field to show all fields available for the current game mode."));
734 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
735 "include/exclude ALL teams/noteams game modes."));
738 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
739 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
740 "right of the vertical bar aligned to the right."));
741 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
742 "other gamemodes except DM."));
745 // NOTE: adding a gametype with ? to not warn for an optional field
746 // make sure it's excluded in a previous exclusive rule, if any
747 // otherwise the previous exclusive rule warns anyway
748 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
749 #define SCOREBOARD_DEFAULT_COLUMNS \
750 "ping pl fps name |" \
751 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
752 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
754 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
755 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
756 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
757 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
758 " +surv/survivals +surv/hunts" \
759 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
760 " +lms/lives +lms/rank" \
761 " +kh/kckills +kh/losses +kh/caps" \
762 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
763 " +as/objectives +nb/faults +nb/goals" \
764 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
765 " +dom/ticks +dom/takes" \
766 " -lms,rc,cts,inv,nb/score"
768 void Cmd_Scoreboard_SetFields(int argc)
773 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
777 return; // do nothing, we don't know gametype and scores yet
779 // sbt_fields uses strunzone on the titles!
780 if(!sbt_field_title[0])
781 for(i = 0; i < MAX_SBT_FIELDS; ++i)
782 sbt_field_title[i] = strzone("(null)");
784 // TODO: re enable with gametype dependant cvars?
785 if(argc < 3) // no arguments provided
786 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
789 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
793 if(argv(2) == "default" || argv(2) == "expand_default")
795 if(argv(2) == "expand_default")
796 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
797 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
799 else if(argv(2) == "all" || argv(2) == "ALL")
801 string s = "ping pl name |"; // scores without label (not really scores)
804 // scores without label
805 s = strcat(s, " ", "sum");
806 s = strcat(s, " ", "kdratio");
807 s = strcat(s, " ", "frags");
809 FOREACH(Scores, true, {
811 if(it != ps_secondary)
812 if(scores_label(it) != "")
813 s = strcat(s, " ", scores_label(it));
815 if(ps_secondary != ps_primary)
816 s = strcat(s, " ", scores_label(ps_secondary));
817 s = strcat(s, " ", scores_label(ps_primary));
818 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
825 hud_fontsize = HUD_GetFontsize("hud_fontsize");
827 for(i = 1; i < argc - 1; ++i)
830 bool nocomplain = false;
831 if(substring(str, 0, 1) == "?")
834 str = substring(str, 1, strlen(str) - 1);
837 slash = strstrofs(str, "/", 0);
840 pattern = substring(str, 0, slash);
841 str = substring(str, slash + 1, strlen(str) - (slash + 1));
843 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
847 str = strtolower(str);
848 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
853 // fields without a label (not networked via the score system)
854 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
855 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
856 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
857 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
858 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
859 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
860 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
861 default: // fields with a label
863 // map alternative labels
864 if (str == "damage") str = "dmg";
865 if (str == "damagetaken") str = "dmgtaken";
867 FOREACH(Scores, true, {
868 if (str == strtolower(scores_label(it))) {
870 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
874 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
875 if(!nocomplain && str != "fps") // server can disable the fps field
876 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
878 strfree(sbt_field_title[sbt_num_fields]);
882 sbt_field[sbt_num_fields] = j;
885 if(j == ps_secondary)
886 have_secondary = true;
891 if(sbt_num_fields >= MAX_SBT_FIELDS)
895 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
897 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
898 have_secondary = true;
899 if(ps_primary == ps_secondary)
900 have_secondary = true;
901 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
903 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
907 strfree(sbt_field_title[sbt_num_fields]);
908 for(i = sbt_num_fields; i > 0; --i)
910 sbt_field_title[i] = sbt_field_title[i-1];
911 sbt_field[i] = sbt_field[i-1];
913 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
914 sbt_field[0] = SP_NAME;
916 LOG_INFO("fixed missing field 'name'");
920 strfree(sbt_field_title[sbt_num_fields]);
921 for(i = sbt_num_fields; i > 1; --i)
923 sbt_field_title[i] = sbt_field_title[i-1];
924 sbt_field[i] = sbt_field[i-1];
926 sbt_field_title[1] = strzone("|");
927 sbt_field[1] = SP_SEPARATOR;
929 LOG_INFO("fixed missing field '|'");
932 else if(!have_separator)
934 strcpy(sbt_field_title[sbt_num_fields], "|");
935 sbt_field[sbt_num_fields] = SP_SEPARATOR;
937 LOG_INFO("fixed missing field '|'");
941 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
942 sbt_field[sbt_num_fields] = ps_secondary;
944 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
948 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
949 sbt_field[sbt_num_fields] = ps_primary;
951 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
955 sbt_field[sbt_num_fields] = SP_END;
956 sb_field_sizes_init = 1;
959 string Scoreboard_AddPlayerId(string pl_name, entity pl)
961 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
962 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
963 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
967 vector sbt_field_rgb;
968 string sbt_field_icon0;
969 string sbt_field_icon1;
970 string sbt_field_icon2;
971 vector sbt_field_icon0_rgb;
972 vector sbt_field_icon1_rgb;
973 vector sbt_field_icon2_rgb;
974 string Scoreboard_GetName(entity pl)
976 if(ready_waiting && pl.ready)
978 sbt_field_icon0 = "gfx/scoreboard/player_ready";
983 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
984 if (playerslots[pl.sv_entnum].colormap >= 1024)
985 f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
987 f = entcs_GetClientColors(pl.sv_entnum);
990 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
991 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
992 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
993 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
994 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
997 return entcs_GetName(pl.sv_entnum);
1000 int autocvar_hud_panel_scoreboard_ping_best = 0;
1001 int autocvar_hud_panel_scoreboard_ping_medium = 70;
1002 int autocvar_hud_panel_scoreboard_ping_high = 100;
1003 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1004 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1005 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1006 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1007 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1008 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1009 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1010 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1011 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1012 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1013 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1014 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1015 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1016 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1018 float tmp, num, denom;
1021 sbt_field_rgb = '1 1 1';
1022 sbt_field_icon0 = "";
1023 sbt_field_icon1 = "";
1024 sbt_field_icon2 = "";
1025 sbt_field_icon0_rgb = '1 1 1';
1026 sbt_field_icon1_rgb = '1 1 1';
1027 sbt_field_icon2_rgb = '1 1 1';
1028 int rounds_played = 0;
1030 rounds_played = pl.(scores(SP_ROUNDS_PL));
1035 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1036 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1041 sbt_field_rgb = COLOR_BEST;
1042 else if(f < PING_MED)
1043 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1044 else if(f < PING_HIGH)
1045 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1046 else if(f < PING_WORST)
1047 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1049 sbt_field_rgb = COLOR_WORST;
1055 f = pl.ping_packetloss;
1056 tmp = pl.ping_movementloss;
1057 if(f == 0 && tmp == 0)
1059 str = ftos(ceil(f * 100));
1061 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1062 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1063 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1067 str = Scoreboard_GetName(pl);
1068 if (autocvar_hud_panel_scoreboard_playerid)
1069 str = Scoreboard_AddPlayerId(str, pl);
1073 f = pl.(scores(SP_KILLS));
1074 f -= pl.(scores(SP_SUICIDES));
1076 return sprintf("%.1f", f / rounds_played);
1080 num = pl.(scores(SP_KILLS));
1081 denom = pl.(scores(SP_DEATHS));
1084 sbt_field_rgb = '0 1 0';
1086 str = sprintf("%.1f", num / rounds_played);
1088 str = sprintf("%d", num);
1089 } else if(num <= 0) {
1090 sbt_field_rgb = '1 0 0';
1092 str = sprintf("%.2f", num / (denom * rounds_played));
1094 str = sprintf("%.1f", num / denom);
1098 str = sprintf("%.2f", num / (denom * rounds_played));
1100 str = sprintf("%.1f", num / denom);
1105 f = pl.(scores(SP_KILLS));
1106 f -= pl.(scores(SP_DEATHS));
1109 sbt_field_rgb = '0 1 0';
1111 sbt_field_rgb = '1 1 1';
1113 sbt_field_rgb = '1 0 0';
1116 return sprintf("%.1f", f / rounds_played);
1121 float elo = pl.(scores(SP_ELO));
1123 case -1: return "...";
1124 case -2: return _("N/A");
1125 default: return ftos(elo);
1131 float fps = pl.(scores(SP_FPS));
1134 sbt_field_rgb = '1 1 1';
1135 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1137 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1138 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1143 return ftos(pl.(scores(field)));
1145 case SP_DMG: case SP_DMGTAKEN:
1147 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1148 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1150 default: case SP_SCORE:
1151 tmp = pl.(scores(field));
1152 f = scores_flags(field);
1153 if(field == ps_primary)
1154 sbt_field_rgb = '1 1 0';
1155 else if(field == ps_secondary)
1156 sbt_field_rgb = '0 1 1';
1158 sbt_field_rgb = '1 1 1';
1159 return ScoreString(f, tmp, rounds_played);
1164 float sbt_fixcolumnwidth_len;
1165 float sbt_fixcolumnwidth_iconlen;
1166 float sbt_fixcolumnwidth_marginlen;
1168 string Scoreboard_FixColumnWidth(int i, string str, bool init)
1174 sbt_fixcolumnwidth_iconlen = 0;
1176 if(sbt_field_icon0 != "")
1178 sz = draw_getimagesize(sbt_field_icon0);
1180 if(sbt_fixcolumnwidth_iconlen < f)
1181 sbt_fixcolumnwidth_iconlen = f;
1184 if(sbt_field_icon1 != "")
1186 sz = draw_getimagesize(sbt_field_icon1);
1188 if(sbt_fixcolumnwidth_iconlen < f)
1189 sbt_fixcolumnwidth_iconlen = f;
1192 if(sbt_field_icon2 != "")
1194 sz = draw_getimagesize(sbt_field_icon2);
1196 if(sbt_fixcolumnwidth_iconlen < f)
1197 sbt_fixcolumnwidth_iconlen = f;
1200 if(sbt_fixcolumnwidth_iconlen != 0)
1202 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1203 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1206 sbt_fixcolumnwidth_marginlen = 0;
1209 sbt_field_title_width[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1211 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1214 float remaining_space = 0;
1215 for(j = 0; j < sbt_num_fields; ++j)
1217 if (sbt_field[i] != SP_SEPARATOR)
1218 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1219 sbt_field_size[i] = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1221 if (sbt_fixcolumnwidth_iconlen != 0)
1222 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1223 float namesize = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1224 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1225 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1227 max_namesize = max(sbt_field_title_width[i], vid_conwidth - remaining_space);
1233 sbt_field_size[i] = sbt_field_title_width[i];
1234 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1235 if (sbt_field_size[i] && sbt_field_size[i] > title_maxwidth)
1236 sbt_field_size[i] = title_maxwidth;
1238 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1241 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1242 if(sbt_field_size[i] < f)
1243 sbt_field_size[i] = f;
1245 sbt_field_title_condense_factor[i] = 0;
1246 if (sbt_field_title_width[i] > sbt_field_size[i])
1248 float real_maxwidth = sbt_field_size[i];
1249 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1250 if (sbt_field_title_width[i] > title_maxwidth)
1251 real_maxwidth = max(sbt_field_size[i], title_maxwidth);
1252 sbt_field_title_condense_factor[i] = real_maxwidth / sbt_field_title_width[i];
1258 void Scoreboard_initFieldSizes(bool compress_more)
1262 float sbt_field_title_maxwidth_factor_prev = sbt_field_title_maxwidth_factor;
1263 sbt_field_title_maxwidth_factor -= 0.05;
1264 if (sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor < 0.01 * vid_conwidth)
1266 sbt_field_title_maxwidth_factor = (0.01 * vid_conwidth) / sbt_field_title_maxwidth;
1267 if (sbt_field_title_maxwidth_factor_prev == sbt_field_title_maxwidth_factor)
1272 sbt_field_title_maxwidth_factor = 1;
1274 for(int i = 0; i < sbt_num_fields; ++i)
1276 if (sbt_field[i] == SP_NAME)
1278 name_field_index = i;
1282 Scoreboard_FixColumnWidth(i, "", true);
1285 // update name field size in the end as it takes remaining space
1286 Scoreboard_FixColumnWidth(name_field_index, "", true);
1289 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1292 vector column_dim = eY * panel_size.y;
1294 column_dim.y -= 1.25 * hud_fontsize.y;
1295 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1296 pos.x += hud_fontsize.x * 0.5;
1297 for(i = 0; i < sbt_num_fields; ++i)
1299 if(sbt_field[i] == SP_SEPARATOR)
1301 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1304 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1305 vector prev_drawfontscale = drawfontscale;
1306 if (sbt_field_title_condense_factor[i])
1307 drawfontscale.x *= sbt_field_title_condense_factor[i];
1308 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1309 if (sbt_field_title_condense_factor[i])
1311 drawfontscale.x *= sbt_field_title_condense_factor[i];
1312 drawfontscale = prev_drawfontscale;
1314 pos.x += column_dim.x;
1317 if(sbt_field[i] == SP_SEPARATOR)
1319 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1320 for(i = sbt_num_fields - 1; i > 0; --i)
1322 if(sbt_field[i] == SP_SEPARATOR)
1325 pos.x -= sbt_field_size[i];
1330 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1331 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1334 vector prev_drawfontscale = drawfontscale;
1335 float titlewidth = stringwidth(sbt_field_title[i], false, hud_fontsize);
1336 if (sbt_field_title_condense_factor[i])
1338 drawfontscale.x *= sbt_field_title_condense_factor[i];
1339 text_offset.x = sbt_field_size[i] - titlewidth * sbt_field_title_condense_factor[i];
1342 text_offset.x = sbt_field_size[i] - titlewidth;
1343 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1344 if (sbt_field_title_condense_factor[i])
1346 drawfontscale.x *= sbt_field_title_condense_factor[i];
1347 drawfontscale = prev_drawfontscale;
1349 pos.x -= hud_fontsize.x;
1353 pos.x = panel_pos.x;
1354 pos.y += 1.25 * hud_fontsize.y;
1358 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1360 TC(bool, is_self); TC(int, pl_number);
1362 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1364 vector h_pos = item_pos;
1365 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1366 // alternated rows highlighting
1367 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1369 if (pl == scoreboard_selected_player)
1370 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1373 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1374 else if((sbt_highlight) && (!(pl_number % 2)))
1375 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1377 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1379 vector pos = item_pos;
1380 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1382 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1384 pos.x += hud_fontsize.x * 0.5;
1385 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1386 vector tmp = '0 0 0';
1388 PlayerScoreField field;
1389 for(i = 0; i < sbt_num_fields; ++i)
1391 field = sbt_field[i];
1392 if(field == SP_SEPARATOR)
1395 if(is_spec && field != SP_NAME && field != SP_PING) {
1396 pos.x += sbt_field_size[i] + hud_fontsize.x;
1399 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1400 str = Scoreboard_FixColumnWidth(i, str, false);
1402 pos.x += sbt_field_size[i] + hud_fontsize.x;
1404 if(field == SP_NAME) {
1405 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1406 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1408 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1409 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1412 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1413 if(sbt_field_icon0 != "")
1414 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1415 if(sbt_field_icon1 != "")
1416 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1417 if(sbt_field_icon2 != "")
1418 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1421 if(sbt_field[i] == SP_SEPARATOR)
1423 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1424 for(i = sbt_num_fields-1; i > 0; --i)
1426 field = sbt_field[i];
1427 if(field == SP_SEPARATOR)
1430 if(is_spec && field != SP_NAME && field != SP_PING) {
1431 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1435 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1436 str = Scoreboard_FixColumnWidth(i, str, false);
1438 if(field == SP_NAME) {
1439 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1440 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1442 tmp.x = sbt_fixcolumnwidth_len;
1443 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1446 tmp.x = sbt_field_size[i];
1447 if(sbt_field_icon0 != "")
1448 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1449 if(sbt_field_icon1 != "")
1450 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1451 if(sbt_field_icon2 != "")
1452 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1453 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1458 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1461 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1464 vector h_pos = item_pos;
1465 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1467 bool complete = (this_team == NUM_SPECTATOR);
1470 if((sbt_highlight) && (!(pl_number % 2)))
1471 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1473 vector pos = item_pos;
1474 pos.x += hud_fontsize.x * 0.5;
1475 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1477 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1479 width_limit -= stringwidth("...", false, hud_fontsize);
1480 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1481 static float max_name_width = 0;
1483 float fieldsize = 0;
1484 float min_fieldsize = 0;
1485 float fieldpadding = hud_fontsize.x * 0.25;
1486 if(this_team == NUM_SPECTATOR)
1488 if(autocvar_hud_panel_scoreboard_spectators_showping)
1489 min_fieldsize = stringwidth("999", false, hud_fontsize);
1491 else if(autocvar_hud_panel_scoreboard_others_showscore)
1492 min_fieldsize = stringwidth("99", false, hud_fontsize);
1493 for(i = 0; pl; pl = pl.sort_next)
1495 if(pl.team != this_team)
1497 if(pl == ignored_pl)
1501 if(this_team == NUM_SPECTATOR)
1503 if(autocvar_hud_panel_scoreboard_spectators_showping)
1504 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1506 else if(autocvar_hud_panel_scoreboard_others_showscore)
1507 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1509 string str = entcs_GetName(pl.sv_entnum);
1510 if (autocvar_hud_panel_scoreboard_playerid)
1511 str = Scoreboard_AddPlayerId(str, pl);
1512 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1513 float column_width = stringwidth(str, true, hud_fontsize);
1514 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1516 if(column_width > max_name_width)
1517 max_name_width = column_width;
1518 column_width = max_name_width;
1522 fieldsize = stringwidth(field, false, hud_fontsize);
1523 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1526 if(pos.x + column_width > width_limit)
1531 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1536 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1537 pos.y += hud_fontsize.y * 1.25;
1541 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1543 if (pl == scoreboard_selected_player)
1545 h_size.x = column_width + hud_fontsize.x * 0.25;
1546 h_size.y = hud_fontsize.y;
1547 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1551 vector name_pos = pos;
1552 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1553 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1554 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1557 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1558 h_size.y = hud_fontsize.y;
1559 vector field_pos = pos;
1560 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1561 field_pos.x += column_width - h_size.x;
1563 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1564 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1565 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1569 h_size.x = column_width + hud_fontsize.x * 0.25;
1570 h_size.y = hud_fontsize.y;
1571 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1573 pos.x += column_width;
1574 pos.x += hud_fontsize.x;
1576 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1579 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1581 int max_players = 999;
1582 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1584 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1587 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1588 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1589 height /= team_count;
1592 height -= panel_bg_padding * 2; // - padding
1593 max_players = floor(height / (hud_fontsize.y * 1.25));
1594 if(max_players <= 1)
1596 if(max_players == tm.team_size)
1601 entity me = playerslots[current_player];
1603 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1604 panel_size.y += panel_bg_padding * 2;
1606 vector scoreboard_selected_hl_pos = pos;
1607 vector scoreboard_selected_hl_size = '0 0 0';
1608 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1609 scoreboard_selected_hl_size.y = panel_size.y;
1613 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1614 if(panel.current_panel_bg != "0")
1615 end_pos.y += panel_bg_border * 2;
1617 if(panel_bg_padding)
1619 panel_pos += '1 1 0' * panel_bg_padding;
1620 panel_size -= '2 2 0' * panel_bg_padding;
1624 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1628 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1630 pos.y += 1.25 * hud_fontsize.y;
1633 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1635 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1638 // print header row and highlight columns
1639 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1641 // fill the table and draw the rows
1642 bool is_self = false;
1643 bool self_shown = false;
1645 for(pl = players.sort_next; pl; pl = pl.sort_next)
1647 if(pl.team != tm.team)
1649 if(i == max_players - 2 && pl != me)
1651 if(!self_shown && me.team == tm.team)
1653 Scoreboard_DrawItem(pos, rgb, me, true, i);
1655 pos.y += 1.25 * hud_fontsize.y;
1659 if(i >= max_players - 1)
1661 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1664 is_self = (pl.sv_entnum == current_player);
1665 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1668 pos.y += 1.25 * hud_fontsize.y;
1672 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1674 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1676 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1677 _alpha *= panel_fg_alpha;
1679 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1683 panel_size.x += panel_bg_padding * 2; // restore initial width
1687 bool Scoreboard_WouldDraw()
1689 if (scoreboard_ui_enabled)
1691 if (scoreboard_ui_disabling)
1693 if (scoreboard_fade_alpha == 0)
1694 HUD_Scoreboard_UI_Disable_Instantly();
1697 if (intermission && scoreboard_ui_enabled == 2)
1699 HUD_Scoreboard_UI_Disable_Instantly();
1704 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1706 else if (QuickMenu_IsOpened())
1708 else if (HUD_Radar_Clickable())
1710 else if (sb_showscores) // set by +showscores engine command
1712 else if (intermission == 1)
1714 else if (intermission == 2)
1716 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1717 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1721 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1726 float average_accuracy;
1727 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1729 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1731 WepSet weapons_stat = WepSet_GetFromStat();
1732 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1733 int disownedcnt = 0;
1735 FOREACH(Weapons, it != WEP_Null, {
1736 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1738 WepSet set = it.m_wepset;
1739 if(it.spawnflags & WEP_TYPE_OTHER)
1744 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1746 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1753 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1754 if (weapon_cnt <= 0) return pos;
1757 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1759 int columns = ceil(weapon_cnt / rows);
1761 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1762 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1763 float height = weapon_height + hud_fontsize.y;
1765 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);
1766 pos.y += 1.25 * hud_fontsize.y;
1767 if(panel.current_panel_bg != "0")
1768 pos.y += panel_bg_border;
1771 panel_size.y = height * rows;
1772 panel_size.y += panel_bg_padding * 2;
1774 float panel_bg_alpha_save = panel_bg_alpha;
1775 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1777 panel_bg_alpha = panel_bg_alpha_save;
1779 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1780 if(panel.current_panel_bg != "0")
1781 end_pos.y += panel_bg_border * 2;
1783 if(panel_bg_padding)
1785 panel_pos += '1 1 0' * panel_bg_padding;
1786 panel_size -= '2 2 0' * panel_bg_padding;
1790 vector tmp = panel_size;
1792 float weapon_width = tmp.x / columns / rows;
1795 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1799 // column highlighting
1800 for (int i = 0; i < columns; ++i)
1802 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);
1805 for (int i = 0; i < rows; ++i)
1806 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1809 average_accuracy = 0;
1810 int weapons_with_stats = 0;
1812 pos.x += weapon_width / 2;
1814 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1817 Accuracy_LoadColors();
1819 float oldposx = pos.x;
1823 FOREACH(Weapons, it != WEP_Null, {
1824 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1826 WepSet set = it.m_wepset;
1827 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1829 if (it.spawnflags & WEP_TYPE_OTHER)
1833 if (weapon_stats >= 0)
1834 weapon_alpha = sbt_fg_alpha;
1836 weapon_alpha = 0.2 * sbt_fg_alpha;
1839 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1841 if (weapon_stats >= 0) {
1842 weapons_with_stats += 1;
1843 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1845 string s = sprintf("%d%%", weapon_stats * 100);
1846 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1848 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1849 rgb = Accuracy_GetColor(weapon_stats);
1851 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1853 tmpos.x += weapon_width * rows;
1854 pos.x += weapon_width * rows;
1855 if (rows == 2 && column == columns - 1) {
1863 if (weapons_with_stats)
1864 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1866 panel_size.x += panel_bg_padding * 2; // restore initial width
1871 bool is_item_filtered(entity it)
1873 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1875 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1878 if (it.instanceOfArmor || it.instanceOfHealth)
1880 int ha_mask = floor(mask) % 10;
1883 default: return false;
1884 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1885 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1886 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1887 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1890 if (it.instanceOfAmmo)
1892 int ammo_mask = floor(mask / 10) % 10;
1893 return (ammo_mask == 1);
1898 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1900 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1902 int disowned_cnt = 0;
1903 int uninteresting_cnt = 0;
1904 IL_EACH(default_order_items, true, {
1905 int q = g_inventory.inv_items[it.m_id];
1906 //q = 1; // debug: display all items
1907 if (is_item_filtered(it))
1908 ++uninteresting_cnt;
1912 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1913 int n = items_cnt - disowned_cnt;
1914 if (n <= 0) return pos;
1916 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1917 int columns = max(6, ceil(n / rows));
1919 float item_height = hud_fontsize.y * 2.3;
1920 float height = item_height + hud_fontsize.y;
1922 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1923 pos.y += 1.25 * hud_fontsize.y;
1924 if(panel.current_panel_bg != "0")
1925 pos.y += panel_bg_border;
1928 panel_size.y = height * rows;
1929 panel_size.y += panel_bg_padding * 2;
1931 float panel_bg_alpha_save = panel_bg_alpha;
1932 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1934 panel_bg_alpha = panel_bg_alpha_save;
1936 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1937 if(panel.current_panel_bg != "0")
1938 end_pos.y += panel_bg_border * 2;
1940 if(panel_bg_padding)
1942 panel_pos += '1 1 0' * panel_bg_padding;
1943 panel_size -= '2 2 0' * panel_bg_padding;
1947 vector tmp = panel_size;
1949 float item_width = tmp.x / columns / rows;
1952 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1956 // column highlighting
1957 for (int i = 0; i < columns; ++i)
1959 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);
1962 for (int i = 0; i < rows; ++i)
1963 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1967 pos.x += item_width / 2;
1969 float oldposx = pos.x;
1973 IL_EACH(default_order_items, !is_item_filtered(it), {
1974 int n = g_inventory.inv_items[it.m_id];
1975 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1976 if (n <= 0) continue;
1977 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);
1979 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1980 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1981 tmpos.x += item_width * rows;
1982 pos.x += item_width * rows;
1983 if (rows == 2 && column == columns - 1) {
1991 panel_size.x += panel_bg_padding * 2; // restore initial width
1996 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1998 pos.x += hud_fontsize.x * 0.25;
1999 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2000 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
2001 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2003 pos.y += hud_fontsize.y;
2008 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
2009 float stat_secrets_found, stat_secrets_total;
2010 float stat_monsters_killed, stat_monsters_total;
2014 // get monster stats
2015 stat_monsters_killed = STAT(MONSTERS_KILLED);
2016 stat_monsters_total = STAT(MONSTERS_TOTAL);
2018 // get secrets stats
2019 stat_secrets_found = STAT(SECRETS_FOUND);
2020 stat_secrets_total = STAT(SECRETS_TOTAL);
2022 // get number of rows
2023 if(stat_secrets_total)
2025 if(stat_monsters_total)
2028 // if no rows, return
2032 // draw table header
2033 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2034 pos.y += 1.25 * hud_fontsize.y;
2035 if(panel.current_panel_bg != "0")
2036 pos.y += panel_bg_border;
2039 panel_size.y = hud_fontsize.y * rows;
2040 panel_size.y += panel_bg_padding * 2;
2043 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2044 if(panel.current_panel_bg != "0")
2045 end_pos.y += panel_bg_border * 2;
2047 if(panel_bg_padding)
2049 panel_pos += '1 1 0' * panel_bg_padding;
2050 panel_size -= '2 2 0' * panel_bg_padding;
2054 vector tmp = panel_size;
2057 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2060 if(stat_monsters_total)
2062 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2063 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2067 if(stat_secrets_total)
2069 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2070 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2073 panel_size.x += panel_bg_padding * 2; // restore initial width
2077 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2080 RANKINGS_RECEIVED_CNT = 0;
2081 for (i=RANKINGS_CNT-1; i>=0; --i)
2083 ++RANKINGS_RECEIVED_CNT;
2085 if (RANKINGS_RECEIVED_CNT == 0)
2088 vector hl_rgb = rgb + '0.5 0.5 0.5';
2090 vector scoreboard_selected_hl_pos = pos;
2092 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2093 pos.y += 1.25 * hud_fontsize.y;
2094 if(panel.current_panel_bg != "0")
2095 pos.y += panel_bg_border;
2097 vector scoreboard_selected_hl_size = '0 0 0';
2098 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2099 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2104 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2106 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2111 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2113 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2117 float ranksize = 3 * hud_fontsize.x;
2118 float timesize = 5 * hud_fontsize.x;
2119 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2120 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2121 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2124 rankings_cnt = RANKINGS_RECEIVED_CNT;
2125 rankings_rows = ceil(rankings_cnt / rankings_columns);
2128 // expand name column to fill the entire row
2129 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2130 namesize += available_space;
2131 columnsize.x += available_space;
2133 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2134 panel_size.y += panel_bg_padding * 2;
2135 scoreboard_selected_hl_size.y += panel_size.y;
2139 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2140 if(panel.current_panel_bg != "0")
2141 end_pos.y += panel_bg_border * 2;
2143 if(panel_bg_padding)
2145 panel_pos += '1 1 0' * panel_bg_padding;
2146 panel_size -= '2 2 0' * panel_bg_padding;
2152 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2154 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2156 int column = 0, j = 0;
2157 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2158 int start_item = rankings_start_column * rankings_rows;
2159 for(i = start_item; i < start_item + rankings_cnt; ++i)
2161 int t = grecordtime[i];
2165 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2166 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2167 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2168 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2170 str = count_ordinal(i+1);
2171 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2172 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2173 str = ColorTranslateRGB(grecordholder[i]);
2175 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2176 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2178 pos.y += 1.25 * hud_fontsize.y;
2180 if(j >= rankings_rows)
2184 pos.x += panel_size.x / rankings_columns;
2185 pos.y = panel_pos.y;
2188 strfree(zoned_name_self);
2190 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2192 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2193 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2196 panel_size.x += panel_bg_padding * 2; // restore initial width
2200 bool have_weapon_stats;
2201 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2203 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2205 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2208 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2209 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2215 if (!have_weapon_stats)
2217 FOREACH(Weapons, it != WEP_Null, {
2218 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2219 if (weapon_stats >= 0)
2221 have_weapon_stats = true;
2225 if (!have_weapon_stats)
2232 bool have_item_stats;
2233 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2235 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2237 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2240 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2241 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2247 if (!have_item_stats)
2249 IL_EACH(default_order_items, true, {
2250 if (!is_item_filtered(it))
2252 int q = g_inventory.inv_items[it.m_id];
2253 //q = 1; // debug: display all items
2256 have_item_stats = true;
2261 if (!have_item_stats)
2268 vector Scoreboard_Spectators_Draw(vector pos) {
2273 for(pl = players.sort_next; pl; pl = pl.sort_next)
2275 if(pl.team == NUM_SPECTATOR)
2277 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2278 if(tm.team == NUM_SPECTATOR)
2280 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2281 draw_beginBoldFont();
2282 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2284 pos.y += 1.25 * hud_fontsize.y;
2286 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2287 pos.y += 1.25 * hud_fontsize.y;
2292 if (str != "") // if there's at least one spectator
2293 pos.y += 0.5 * hud_fontsize.y;
2298 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2300 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2301 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2302 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2303 (s_label == "score") ? CTX(_("SCO^points")) :
2304 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2307 void Scoreboard_Draw()
2309 if(!autocvar__hud_configure)
2311 if(!hud_draw_maximized) return;
2313 // frametime checks allow to toggle the scoreboard even when the game is paused
2314 if(scoreboard_active) {
2315 if (scoreboard_fade_alpha == 0)
2316 scoreboard_time = time;
2317 if(hud_configure_menu_open == 1)
2318 scoreboard_fade_alpha = 1;
2319 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2320 if (scoreboard_fadeinspeed && frametime)
2321 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2323 scoreboard_fade_alpha = 1;
2325 static string hud_fontsize_str;
2326 if(hud_fontsize_str != autocvar_hud_fontsize)
2328 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2329 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2330 sb_field_sizes_init = 1;
2333 static float scoreboard_table_fieldtitle_maxwidth_prev;
2334 if (scoreboard_table_fieldtitle_maxwidth_prev != autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth)
2336 scoreboard_table_fieldtitle_maxwidth_prev = autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth;
2337 sbt_field_title_maxwidth = bound(0.01, autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth, 0.1);
2338 sbt_field_title_maxwidth *= vid_conwidth;
2339 sb_field_sizes_init = 1;
2343 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2344 if (scoreboard_fadeoutspeed && frametime)
2345 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2347 scoreboard_fade_alpha = 0;
2350 if (!scoreboard_fade_alpha)
2352 scoreboard_acc_fade_alpha = 0;
2353 scoreboard_itemstats_fade_alpha = 0;
2358 scoreboard_fade_alpha = 0;
2360 if (autocvar_hud_panel_scoreboard_dynamichud)
2363 HUD_Scale_Disable();
2365 if(scoreboard_fade_alpha <= 0)
2367 panel_fade_alpha *= scoreboard_fade_alpha;
2368 HUD_Panel_LoadCvars();
2370 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2371 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2372 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2373 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2374 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2375 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2376 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2378 // don't overlap with con_notify
2379 if(!autocvar__hud_configure)
2380 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2382 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2383 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2384 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2385 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2386 panel_pos.x = scoreboard_left;
2387 panel_size.x = fixed_scoreboard_width;
2389 // field sizes can be initialized now after panel_size.x calculation
2390 if (!sbt_field_size[0] || sb_field_sizes_init)
2392 bool compress = (sb_field_sizes_init == 2);
2393 Scoreboard_initFieldSizes(compress);
2394 sb_field_sizes_init = 0;
2397 Scoreboard_UpdatePlayerTeams();
2399 scoreboard_top = panel_pos.y;
2400 vector pos = panel_pos;
2405 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2407 // Begin of Game Info Section
2408 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2409 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2411 // Game Info: Game Type
2412 if (scoreboard_ui_enabled == 2)
2413 str = _("Team Selection");
2414 else if (gametype_custom_name != "")
2415 str = gametype_custom_name;
2417 str = MapInfo_Type_ToText(gametype);
2418 draw_beginBoldFont();
2419 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);
2422 pos.y += sb_gameinfo_type_fontsize.y;
2423 // Game Info: Game Detail
2424 if (scoreboard_ui_enabled == 2)
2426 if (scoreboard_selected_team)
2427 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2429 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2430 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);
2432 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2433 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2434 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);
2438 float tl = STAT(TIMELIMIT);
2439 float fl = STAT(FRAGLIMIT);
2440 float ll = STAT(LEADLIMIT);
2441 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2444 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2445 if(!gametype.m_hidelimits)
2450 str = strcat(str, "^7 / "); // delimiter
2451 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2455 if(tl > 0 || fl > 0)
2458 if (ll_and_fl && fl > 0)
2459 str = strcat(str, "^7 & ");
2461 str = strcat(str, "^7 / ");
2463 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2466 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
2467 // map name and player count
2471 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2472 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2473 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2475 // End of Game Info Section
2477 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2478 if(panel.current_panel_bg != "0")
2479 pos.y += panel_bg_border;
2481 // Draw the scoreboard
2482 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2485 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2489 vector panel_bg_color_save = panel_bg_color;
2490 vector team_score_baseoffset;
2491 vector team_size_baseoffset;
2492 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2494 // put team score to the left of scoreboard (and team size to the right)
2495 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2496 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2497 if(panel.current_panel_bg != "0")
2499 team_score_baseoffset.x -= panel_bg_border;
2500 team_size_baseoffset.x += panel_bg_border;
2505 // put team score to the right of scoreboard (and team size to the left)
2506 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2507 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2508 if(panel.current_panel_bg != "0")
2510 team_score_baseoffset.x += panel_bg_border;
2511 team_size_baseoffset.x -= panel_bg_border;
2515 int team_size_total = 0;
2516 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2518 // calculate team size total (sum of all team sizes)
2519 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2520 if(tm.team != NUM_SPECTATOR)
2521 team_size_total += tm.team_size;
2524 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2526 if(tm.team == NUM_SPECTATOR)
2531 draw_beginBoldFont();
2532 vector rgb = Team_ColorRGB(tm.team);
2533 str = ftos(tm.(teamscores(ts_primary)));
2534 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2536 // team score on the left (default)
2537 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2541 // team score on the right
2542 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2544 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2546 // team size (if set to show on the side)
2547 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2549 // calculate the starting position for the whole team size info string
2550 str = sprintf("%d/%d", tm.team_size, team_size_total);
2551 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2553 // team size on the left
2554 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2558 // team size on the right
2559 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2561 str = sprintf("%d", tm.team_size);
2562 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2563 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2564 str = sprintf("/%d", team_size_total);
2565 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2569 // secondary score, e.g. keyhunt
2570 if(ts_primary != ts_secondary)
2572 str = ftos(tm.(teamscores(ts_secondary)));
2573 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2576 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2581 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2584 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2587 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2588 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2589 else if(panel_bg_color_team > 0)
2590 panel_bg_color = rgb * panel_bg_color_team;
2592 panel_bg_color = rgb;
2593 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2595 panel_bg_color = panel_bg_color_save;
2599 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2600 if(tm.team != NUM_SPECTATOR)
2603 // display it anyway
2604 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2607 // if the name column is too small, try to compress all other field titles
2608 if (sbt_field_size[name_field_index] < sbt_field_title_width[name_field_index] + hud_fontsize.x)
2609 sb_field_sizes_init = 2;
2611 // draw scoreboard spectators before accuracy and item stats
2612 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2613 pos = Scoreboard_Spectators_Draw(pos);
2616 // draw accuracy and item stats
2617 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2618 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2619 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2620 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2622 // draw scoreboard spectators after accuracy and item stats and before rankings
2623 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2624 pos = Scoreboard_Spectators_Draw(pos);
2627 if(MUTATOR_CALLHOOK(ShowRankings)) {
2628 string ranktitle = M_ARGV(0, string);
2629 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2630 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2631 if(race_speedaward_alltimebest)
2634 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2638 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2639 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2640 str = strcat(str, " / ");
2642 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2643 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2644 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2645 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2647 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2652 // draw scoreboard spectators after rankings
2653 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2654 pos = Scoreboard_Spectators_Draw(pos);
2657 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2659 // draw scoreboard spectators after mapstats
2660 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2661 pos = Scoreboard_Spectators_Draw(pos);
2665 // print information about respawn status
2666 float respawn_time = STAT(RESPAWN_TIME);
2667 if(!intermission && respawn_time)
2669 if(respawn_time < 0)
2671 // a negative number means we are awaiting respawn, time value is still the same
2672 respawn_time *= -1; // remove mark now that we checked it
2674 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2675 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2677 str = sprintf(_("^1Respawning in ^3%s^1..."),
2678 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2679 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2681 count_seconds(ceil(respawn_time - time))
2685 else if(time < respawn_time)
2687 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2688 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2689 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2691 count_seconds(ceil(respawn_time - time))
2695 else if(time >= respawn_time)
2696 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2698 pos.y += 1.2 * hud_fontsize.y;
2699 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2702 pos.y += hud_fontsize.y;
2703 if (scoreboard_fade_alpha < 1)
2704 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2705 else if (pos.y != scoreboard_bottom)
2707 if (pos.y > scoreboard_bottom)
2708 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2710 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2715 if (scoreboard_fade_alpha == 1)
2717 if (scoreboard_bottom > 0.95 * vid_conheight)
2718 rankings_rows = max(1, rankings_rows - 1);
2719 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2720 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2722 rankings_cnt = rankings_rows * rankings_columns;