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;
57 float sbt_fg_alpha_self;
59 float sbt_highlight_alpha;
60 float sbt_highlight_alpha_self;
61 float sbt_highlight_alpha_eliminated;
63 // provide basic panel cvars to old clients
64 // TODO remove them after a future release (0.8.2+)
65 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
66 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
67 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
68 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
69 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
70 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
71 noref string autocvar_hud_panel_scoreboard_bg_border = "";
72 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
74 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
75 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
76 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
77 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
78 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
79 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
80 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
81 float autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth = 0.07;
82 bool autocvar_hud_panel_scoreboard_table_highlight = true;
83 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
84 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
85 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
86 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
87 float autocvar_hud_panel_scoreboard_team_size_position = 0;
88 float autocvar_hud_panel_scoreboard_spectators_position = 1;
90 bool autocvar_hud_panel_scoreboard_accuracy = true;
91 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
93 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
94 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_itemstats = true;
97 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
98 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
99 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
100 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
101 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
103 bool autocvar_hud_panel_scoreboard_dynamichud = false;
105 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
106 bool autocvar_hud_panel_scoreboard_others_showscore = true;
107 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
108 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
109 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
110 bool autocvar_hud_panel_scoreboard_playerid = false;
111 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
112 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
113 bool autocvar_hud_panel_scoreboard_scores_per_round;
115 float scoreboard_time;
119 if(autocvar_hud_panel_scoreboard_scores_per_round)
120 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
123 // mode 0: returns translated label
124 // mode 1: prints name and description of all the labels
125 string Label_getInfo(string label, int mode)
128 label = "bckills"; // first case in the switch
130 #define SCO_LABEL(strlabel, label, padding, help) \
133 return CTX(strlabel); \
134 LOG_HELP("^3", label, padding, "^7", help);
138 SCO_LABEL(_("SCO^bckills"), "bckills", " ", _("Number of ball carrier kills"));
139 SCO_LABEL(_("SCO^bctime"), "bctime", " ", _("Total amount of time holding the ball in Keepaway"));
140 SCO_LABEL(_("SCO^caps"), "caps", " ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
141 SCO_LABEL(_("SCO^captime"), "captime", " ", _("Time of fastest capture (CTF)"));
142 SCO_LABEL(_("SCO^deaths"), "deaths", " ", _("Number of deaths"));
143 SCO_LABEL(_("SCO^destructions"), "destructions", " ", _("Number of keys destroyed by pushing them into void"));
144 SCO_LABEL(_("SCO^damage dealt"), "dmg", " ", _("The total damage dealt"));
145 SCO_LABEL(_("SCO^damage taken"), "dmgtaken", " ", _("The total damage taken"));
146 SCO_LABEL(_("SCO^drops"), "drops", " ", _("Number of flag drops"));
147 SCO_LABEL(_("SCO^elo"), "elo", " ", _("Player ELO"));
148 SCO_LABEL(_("SCO^fastest"), "fastest", " ", _("Time of fastest lap (Race/CTS)"));
149 SCO_LABEL(_("SCO^faults"), "faults", " ", _("Number of faults committed"));
150 SCO_LABEL(_("SCO^fckills"), "fckills", " ", _("Number of flag carrier kills"));
151 SCO_LABEL(_("SCO^fps"), "fps", " ", _("FPS"));
152 SCO_LABEL(_("SCO^frags"), "frags", " ", _("Number of kills minus suicides"));
153 SCO_LABEL(_("SCO^generators"), "generators", " ", _("Number of generators destroyed"));
154 SCO_LABEL(_("SCO^goals"), "goals", " ", _("Number of goals scored"));
155 SCO_LABEL(_("SCO^hunts"), "hunts", " ", _("Number of hunts (Survival)"));
156 SCO_LABEL(_("SCO^kckills"), "kckills", " ", _("Number of keys carrier kills"));
157 SCO_LABEL(_("SCO^k/d"), "kd", " ", _("The kill-death ratio"));
158 SCO_LABEL(_("SCO^kdr"), "kdr", " ", _("The kill-death ratio"));
159 SCO_LABEL(_("SCO^kdratio"), "kdratio", " ", _("The kill-death ratio"));
160 SCO_LABEL(_("SCO^kills"), "kills", " ", _("Number of kills"));
161 SCO_LABEL(_("SCO^laps"), "laps", " ", _("Number of laps finished (Race/CTS)"));
162 SCO_LABEL(_("SCO^lives"), "lives", " ", _("Number of lives (LMS)"));
163 SCO_LABEL(_("SCO^losses"), "losses", " ", _("Number of times a key was lost"));
164 SCO_LABEL(_("SCO^name"), "name", " ", _("Player name"));
165 SCO_LABEL(_("SCO^nick"), "nick", " ", _("Player name"));
166 SCO_LABEL(_("SCO^objectives"), "objectives", " ", _("Number of objectives destroyed"));
167 SCO_LABEL(_("SCO^pickups"), "pickups", " ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
168 SCO_LABEL(_("SCO^ping"), "ping", " ", _("Ping time"));
169 SCO_LABEL(_("SCO^pl"), "pl", " ", _("Packet loss"));
170 SCO_LABEL(_("SCO^pushes"), "pushes", " ", _("Number of players pushed into void"));
171 SCO_LABEL(_("SCO^rank"), "rank", " ", _("Player rank"));
172 SCO_LABEL(_("SCO^returns"), "returns", " ", _("Number of flag returns"));
173 SCO_LABEL(_("SCO^revivals"), "revivals", " ", _("Number of revivals"));
174 SCO_LABEL(_("SCO^rounds won"), "rounds", " ", _("Number of rounds won"));
175 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", " ", _("Number of rounds played"));
176 SCO_LABEL(_("SCO^score"), "score", " ", _("Total score"));
177 SCO_LABEL(_("SCO^suicides"), "suicides", " ", _("Number of suicides"));
178 SCO_LABEL(_("SCO^sum"), "sum", " ", _("Number of kills minus deaths"));
179 SCO_LABEL(_("SCO^survivals"), "survivals", " ", _("Number of survivals"));
180 SCO_LABEL(_("SCO^takes"), "takes", " ", _("Number of domination points taken (Domination)"));
181 SCO_LABEL(_("SCO^teamkills"), "teamkills", " ", _("Number of teamkills"));
182 SCO_LABEL(_("SCO^ticks"), "ticks", " ", _("Number of ticks (Domination)"));
183 SCO_LABEL(_("SCO^time"), "time", " ", _("Total time raced (Race/CTS)"));
189 bool scoreboard_ui_disabling;
190 void HUD_Scoreboard_UI_Disable()
192 scoreboard_ui_disabling = true;
193 sb_showscores = false;
196 void HUD_Scoreboard_UI_Disable_Instantly()
198 scoreboard_ui_disabling = false;
199 scoreboard_ui_enabled = 0;
200 scoreboard_selected_panel = 0;
201 scoreboard_selected_player = NULL;
202 scoreboard_selected_team = NULL;
205 // mode: 0 normal, 1 team selection
206 void Scoreboard_UI_Enable(int mode)
212 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
215 // release player's pressed keys as they aren't released elsewhere
216 // in particular jump needs to be released as it may open the team selection
217 // (when server detects jump has been pressed it sends the command to open the team selection)
218 Release_Common_Keys();
219 scoreboard_ui_enabled = 2;
220 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
224 if (scoreboard_ui_enabled == 1)
226 scoreboard_ui_enabled = 1;
227 scoreboard_selected_panel = SB_PANEL_FIRST;
229 scoreboard_selected_player = NULL;
230 scoreboard_selected_team = NULL;
231 scoreboard_selected_panel_time = time;
234 int rankings_start_column;
235 int rankings_rows = 0;
236 int rankings_columns = 0;
237 int rankings_cnt = 0;
238 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
242 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
247 mousepos.x = nPrimary;
248 mousepos.y = nSecondary;
255 // at this point bInputType can only be 0 or 1 (key pressed or released)
256 bool key_pressed = (bInputType == 0);
258 // ESC to exit (TAB-ESC works too)
259 if(nPrimary == K_ESCAPE)
263 HUD_Scoreboard_UI_Disable();
267 // block any input while a menu dialog is fading
268 if(autocvar__menu_alpha)
274 // allow console bind to work
275 string con_keys = findkeysforcommand("toggleconsole", 0);
276 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
278 bool hit_con_bind = false;
280 for (i = 0; i < keys; ++i)
282 if(nPrimary == stof(argv(i)))
287 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
288 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
289 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
290 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
293 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
294 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
295 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
296 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
299 if(nPrimary == K_TAB)
303 if (scoreboard_ui_enabled == 2)
305 if (hudShiftState & S_SHIFT)
308 goto downarrow_action;
311 if (hudShiftState & S_SHIFT)
313 --scoreboard_selected_panel;
314 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
315 --scoreboard_selected_panel;
316 if (scoreboard_selected_panel < SB_PANEL_FIRST)
317 scoreboard_selected_panel = SB_PANEL_MAX;
321 ++scoreboard_selected_panel;
322 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
323 ++scoreboard_selected_panel;
324 if (scoreboard_selected_panel > SB_PANEL_MAX)
325 scoreboard_selected_panel = SB_PANEL_FIRST;
328 scoreboard_selected_panel_time = time;
330 else if(nPrimary == K_DOWNARROW)
334 LABEL(downarrow_action);
335 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
337 if (scoreboard_ui_enabled == 2)
339 entity curr_team = NULL;
340 bool scoreboard_selected_team_found = false;
341 if (!scoreboard_selected_team)
342 scoreboard_selected_team_found = true;
344 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
346 if(tm.team == NUM_SPECTATOR)
349 if (scoreboard_selected_team_found)
351 if (scoreboard_selected_team == tm)
352 scoreboard_selected_team_found = true;
355 if (curr_team == scoreboard_selected_team) // loop reached the last team
357 scoreboard_selected_team = curr_team;
362 entity curr_pl = NULL;
363 bool scoreboard_selected_player_found = false;
364 if (!scoreboard_selected_player)
365 scoreboard_selected_player_found = true;
367 for(tm = teams.sort_next; tm; tm = tm.sort_next)
369 if(tm.team != NUM_SPECTATOR)
370 for(pl = players.sort_next; pl; pl = pl.sort_next)
372 if(pl.team != tm.team)
375 if (scoreboard_selected_player_found)
377 if (scoreboard_selected_player == pl)
378 scoreboard_selected_player_found = true;
382 if (curr_pl == scoreboard_selected_player) // loop reached the last player
384 scoreboard_selected_player = curr_pl;
388 else if(nPrimary == K_UPARROW)
392 LABEL(uparrow_action);
393 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
395 if (scoreboard_ui_enabled == 2)
397 entity prev_team = NULL;
398 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
400 if(tm.team == NUM_SPECTATOR)
402 if (tm == scoreboard_selected_team)
407 scoreboard_selected_team = prev_team;
411 entity prev_pl = NULL;
413 for(tm = teams.sort_next; tm; tm = tm.sort_next)
415 if(tm.team != NUM_SPECTATOR)
416 for(pl = players.sort_next; pl; pl = pl.sort_next)
418 if(pl.team != tm.team)
420 if (pl == scoreboard_selected_player)
426 scoreboard_selected_player = prev_pl;
430 else if(nPrimary == K_RIGHTARROW)
434 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
435 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
437 else if(nPrimary == K_LEFTARROW)
441 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
442 rankings_start_column = max(rankings_start_column - 1, 0);
444 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
448 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
450 if (scoreboard_ui_enabled == 2)
453 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
456 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
457 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
458 HUD_Scoreboard_UI_Disable();
460 else if (scoreboard_selected_player)
461 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
464 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
468 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
470 switch (scoreboard_selected_columns_layout)
473 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
475 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
476 scoreboard_selected_columns_layout = 1;
481 localcmd(sprintf("scoreboard_columns_set default\n"));
482 scoreboard_selected_columns_layout = 2;
485 localcmd(sprintf("scoreboard_columns_set all\n"));
486 scoreboard_selected_columns_layout = 0;
491 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
495 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
496 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
498 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
502 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
504 if (scoreboard_selected_player)
506 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
507 HUD_Scoreboard_UI_Disable();
511 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
515 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
517 if (scoreboard_selected_player)
518 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
521 else if(hit_con_bind || nPrimary == K_PAUSE)
527 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
528 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
530 void Scoreboard_InitScores()
534 ps_primary = ps_secondary = NULL;
535 ts_primary = ts_secondary = -1;
536 FOREACH(Scores, true, {
537 if(scores_flags(it) & SFL_NOT_SORTABLE)
539 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
540 if(f == SFL_SORT_PRIO_PRIMARY)
542 if(f == SFL_SORT_PRIO_SECONDARY)
545 if(ps_secondary == NULL)
546 ps_secondary = ps_primary;
548 for(i = 0; i < MAX_TEAMSCORE; ++i)
550 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
551 if(f == SFL_SORT_PRIO_PRIMARY)
553 if(f == SFL_SORT_PRIO_SECONDARY)
556 if(ts_secondary == -1)
557 ts_secondary = ts_primary;
559 Cmd_Scoreboard_SetFields(0);
563 void Scoreboard_UpdatePlayerTeams()
565 static float update_time;
566 if (time <= update_time)
573 for(pl = players.sort_next; pl; pl = pl.sort_next)
575 numplayers += pl.team != NUM_SPECTATOR;
577 int Team = entcs_GetScoreTeam(pl.sv_entnum);
578 if(SetTeam(pl, Team))
581 Scoreboard_UpdatePlayerPos(pl);
585 pl = players.sort_next;
590 print(strcat("PNUM: ", ftos(num), "\n"));
595 int Scoreboard_CompareScore(int vl, int vr, int f)
597 TC(int, vl); TC(int, vr); TC(int, f);
598 if(f & SFL_ZERO_IS_WORST)
600 if(vl == 0 && vr != 0)
602 if(vl != 0 && vr == 0)
606 return IS_INCREASING(f);
608 return IS_DECREASING(f);
612 float Scoreboard_ComparePlayerScores(entity left, entity right)
614 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
615 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
622 if(vl == NUM_SPECTATOR)
624 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
626 if(!left.gotscores && right.gotscores)
631 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
632 if (res >= 0) return res;
634 if (ps_secondary && ps_secondary != ps_primary)
636 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
637 if (res >= 0) return res;
640 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
641 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
642 if (res >= 0) return res;
645 if (left.sv_entnum < right.sv_entnum)
651 void Scoreboard_UpdatePlayerPos(entity player)
654 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
656 SORT_SWAP(player, ent);
658 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
660 SORT_SWAP(ent, player);
664 float Scoreboard_CompareTeamScores(entity left, entity right)
666 if(left.team == NUM_SPECTATOR)
668 if(right.team == NUM_SPECTATOR)
673 for(int i = -2; i < MAX_TEAMSCORE; ++i)
677 if (fld_idx == -1) fld_idx = ts_primary;
678 else if (ts_secondary == ts_primary) continue;
679 else fld_idx = ts_secondary;
684 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
687 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
688 if (r >= 0) return r;
691 if (left.team < right.team)
697 void Scoreboard_UpdateTeamPos(entity Team)
700 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
702 SORT_SWAP(Team, ent);
704 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
706 SORT_SWAP(ent, Team);
710 void Cmd_Scoreboard_Help()
712 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
713 LOG_HELP(_("Usage:"));
714 LOG_HELP("^2scoreboard_columns_set ^3default");
715 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
716 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
717 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
718 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
719 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
720 LOG_HELP(_("The following field names are recognized (case insensitive):"));
726 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
727 "of game types, then a slash, to make the field show up only in these\n"
728 "or in all but these game types. You can also specify 'all' as a\n"
729 "field to show all fields available for the current game mode."));
732 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
733 "include/exclude ALL teams/noteams game modes."));
736 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
737 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
738 "right of the vertical bar aligned to the right."));
739 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
740 "other gamemodes except DM."));
743 // NOTE: adding a gametype with ? to not warn for an optional field
744 // make sure it's excluded in a previous exclusive rule, if any
745 // otherwise the previous exclusive rule warns anyway
746 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
747 #define SCOREBOARD_DEFAULT_COLUMNS \
748 "ping pl fps name |" \
749 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
750 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
752 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
753 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
754 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
755 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
756 " +surv/survivals +surv/hunts" \
757 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
758 " +lms/lives +lms/rank" \
759 " +kh/kckills +kh/losses +kh/caps" \
760 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
761 " +as/objectives +nb/faults +nb/goals" \
762 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
763 " +dom/ticks +dom/takes" \
764 " -lms,rc,cts,inv,nb/score"
766 void Cmd_Scoreboard_SetFields(int argc)
771 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
775 return; // do nothing, we don't know gametype and scores yet
777 // sbt_fields uses strunzone on the titles!
778 if(!sbt_field_title[0])
779 for(i = 0; i < MAX_SBT_FIELDS; ++i)
780 sbt_field_title[i] = strzone("(null)");
782 // TODO: re enable with gametype dependant cvars?
783 if(argc < 3) // no arguments provided
784 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
787 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
791 if(argv(2) == "default" || argv(2) == "expand_default")
793 if(argv(2) == "expand_default")
794 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
795 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
797 else if(argv(2) == "all" || argv(2) == "ALL")
799 string s = "ping pl name |"; // scores without label (not really scores)
802 // scores without label
803 s = strcat(s, " ", "sum");
804 s = strcat(s, " ", "kdratio");
805 s = strcat(s, " ", "frags");
807 FOREACH(Scores, true, {
809 if(it != ps_secondary)
810 if(scores_label(it) != "")
811 s = strcat(s, " ", scores_label(it));
813 if(ps_secondary != ps_primary)
814 s = strcat(s, " ", scores_label(ps_secondary));
815 s = strcat(s, " ", scores_label(ps_primary));
816 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
823 hud_fontsize = HUD_GetFontsize("hud_fontsize");
825 for(i = 1; i < argc - 1; ++i)
828 bool nocomplain = false;
829 if(substring(str, 0, 1) == "?")
832 str = substring(str, 1, strlen(str) - 1);
835 slash = strstrofs(str, "/", 0);
838 pattern = substring(str, 0, slash);
839 str = substring(str, slash + 1, strlen(str) - (slash + 1));
841 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
845 str = strtolower(str);
846 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
851 // fields without a label (not networked via the score system)
852 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
853 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
854 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
855 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
856 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
857 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
858 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
859 default: // fields with a label
861 // map alternative labels
862 if (str == "damage") str = "dmg";
863 if (str == "damagetaken") str = "dmgtaken";
865 FOREACH(Scores, true, {
866 if (str == strtolower(scores_label(it))) {
868 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
872 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
873 if(!nocomplain && str != "fps") // server can disable the fps field
874 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
876 strfree(sbt_field_title[sbt_num_fields]);
880 sbt_field[sbt_num_fields] = j;
883 if(j == ps_secondary)
884 have_secondary = true;
889 if(sbt_num_fields >= MAX_SBT_FIELDS)
893 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
895 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
896 have_secondary = true;
897 if(ps_primary == ps_secondary)
898 have_secondary = true;
899 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
901 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
905 strfree(sbt_field_title[sbt_num_fields]);
906 for(i = sbt_num_fields; i > 0; --i)
908 sbt_field_title[i] = sbt_field_title[i-1];
909 sbt_field[i] = sbt_field[i-1];
911 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
912 sbt_field[0] = SP_NAME;
914 LOG_INFO("fixed missing field 'name'");
918 strfree(sbt_field_title[sbt_num_fields]);
919 for(i = sbt_num_fields; i > 1; --i)
921 sbt_field_title[i] = sbt_field_title[i-1];
922 sbt_field[i] = sbt_field[i-1];
924 sbt_field_title[1] = strzone("|");
925 sbt_field[1] = SP_SEPARATOR;
927 LOG_INFO("fixed missing field '|'");
930 else if(!have_separator)
932 strcpy(sbt_field_title[sbt_num_fields], "|");
933 sbt_field[sbt_num_fields] = SP_SEPARATOR;
935 LOG_INFO("fixed missing field '|'");
939 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
940 sbt_field[sbt_num_fields] = ps_secondary;
942 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
946 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
947 sbt_field[sbt_num_fields] = ps_primary;
949 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
953 sbt_field[sbt_num_fields] = SP_END;
954 sbt_field_size[0] = 0; // tells Scoreboard_Draw to initialize all field sizes
957 string Scoreboard_AddPlayerId(string pl_name, entity pl)
959 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
960 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
961 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
965 vector sbt_field_rgb;
966 string sbt_field_icon0;
967 string sbt_field_icon1;
968 string sbt_field_icon2;
969 vector sbt_field_icon0_rgb;
970 vector sbt_field_icon1_rgb;
971 vector sbt_field_icon2_rgb;
972 string Scoreboard_GetName(entity pl)
974 if(ready_waiting && pl.ready)
976 sbt_field_icon0 = "gfx/scoreboard/player_ready";
981 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
982 if (playerslots[pl.sv_entnum].colormap >= 1024)
983 f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
985 f = entcs_GetClientColors(pl.sv_entnum);
988 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
989 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
990 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
991 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
992 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
995 return entcs_GetName(pl.sv_entnum);
998 int autocvar_hud_panel_scoreboard_ping_best = 0;
999 int autocvar_hud_panel_scoreboard_ping_medium = 70;
1000 int autocvar_hud_panel_scoreboard_ping_high = 100;
1001 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1002 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1003 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1004 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1005 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1006 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1007 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1008 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1009 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1010 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1011 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1012 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1013 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1014 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1016 float tmp, num, denom;
1019 sbt_field_rgb = '1 1 1';
1020 sbt_field_icon0 = "";
1021 sbt_field_icon1 = "";
1022 sbt_field_icon2 = "";
1023 sbt_field_icon0_rgb = '1 1 1';
1024 sbt_field_icon1_rgb = '1 1 1';
1025 sbt_field_icon2_rgb = '1 1 1';
1026 int rounds_played = 0;
1028 rounds_played = pl.(scores(SP_ROUNDS_PL));
1033 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1034 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1039 sbt_field_rgb = COLOR_BEST;
1040 else if(f < PING_MED)
1041 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1042 else if(f < PING_HIGH)
1043 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1044 else if(f < PING_WORST)
1045 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1047 sbt_field_rgb = COLOR_WORST;
1053 f = pl.ping_packetloss;
1054 tmp = pl.ping_movementloss;
1055 if(f == 0 && tmp == 0)
1057 str = ftos(ceil(f * 100));
1059 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1060 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1061 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1065 str = Scoreboard_GetName(pl);
1066 if (autocvar_hud_panel_scoreboard_playerid)
1067 str = Scoreboard_AddPlayerId(str, pl);
1071 f = pl.(scores(SP_KILLS));
1072 f -= pl.(scores(SP_SUICIDES));
1074 return sprintf("%.1f", f / rounds_played);
1078 num = pl.(scores(SP_KILLS));
1079 denom = pl.(scores(SP_DEATHS));
1082 sbt_field_rgb = '0 1 0';
1084 str = sprintf("%.1f", num / rounds_played);
1086 str = sprintf("%d", num);
1087 } else if(num <= 0) {
1088 sbt_field_rgb = '1 0 0';
1090 str = sprintf("%.2f", num / (denom * rounds_played));
1092 str = sprintf("%.1f", num / denom);
1096 str = sprintf("%.2f", num / (denom * rounds_played));
1098 str = sprintf("%.1f", num / denom);
1103 f = pl.(scores(SP_KILLS));
1104 f -= pl.(scores(SP_DEATHS));
1107 sbt_field_rgb = '0 1 0';
1109 sbt_field_rgb = '1 1 1';
1111 sbt_field_rgb = '1 0 0';
1114 return sprintf("%.1f", f / rounds_played);
1119 float elo = pl.(scores(SP_ELO));
1121 case -1: return "...";
1122 case -2: return _("N/A");
1123 default: return ftos(elo);
1129 float fps = pl.(scores(SP_FPS));
1132 sbt_field_rgb = '1 1 1';
1133 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1135 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1136 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1141 return ftos(pl.(scores(field)));
1143 case SP_DMG: case SP_DMGTAKEN:
1145 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1146 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1148 default: case SP_SCORE:
1149 tmp = pl.(scores(field));
1150 f = scores_flags(field);
1151 if(field == ps_primary)
1152 sbt_field_rgb = '1 1 0';
1153 else if(field == ps_secondary)
1154 sbt_field_rgb = '0 1 1';
1156 sbt_field_rgb = '1 1 1';
1157 return ScoreString(f, tmp, rounds_played);
1162 float sbt_fixcolumnwidth_len;
1163 float sbt_fixcolumnwidth_iconlen;
1164 float sbt_fixcolumnwidth_marginlen;
1166 string Scoreboard_FixColumnWidth(int i, string str, bool init)
1172 sbt_fixcolumnwidth_iconlen = 0;
1174 if(sbt_field_icon0 != "")
1176 sz = draw_getimagesize(sbt_field_icon0);
1178 if(sbt_fixcolumnwidth_iconlen < f)
1179 sbt_fixcolumnwidth_iconlen = f;
1182 if(sbt_field_icon1 != "")
1184 sz = draw_getimagesize(sbt_field_icon1);
1186 if(sbt_fixcolumnwidth_iconlen < f)
1187 sbt_fixcolumnwidth_iconlen = f;
1190 if(sbt_field_icon2 != "")
1192 sz = draw_getimagesize(sbt_field_icon2);
1194 if(sbt_fixcolumnwidth_iconlen < f)
1195 sbt_fixcolumnwidth_iconlen = f;
1198 if(sbt_fixcolumnwidth_iconlen != 0)
1200 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1201 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1204 sbt_fixcolumnwidth_marginlen = 0;
1207 sbt_field_title_width[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1209 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1212 float remaining_space = 0;
1213 for(j = 0; j < sbt_num_fields; ++j)
1215 if (sbt_field[i] != SP_SEPARATOR)
1216 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1217 sbt_field_size[i] = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1219 if (sbt_fixcolumnwidth_iconlen != 0)
1220 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1221 float namesize = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1222 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1223 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1225 max_namesize = max(sbt_field_title_width[i], vid_conwidth - remaining_space);
1231 sbt_field_size[i] = sbt_field_title_width[i];
1232 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1233 if (sbt_field_size[i] && sbt_field_size[i] > title_maxwidth)
1234 sbt_field_size[i] = title_maxwidth;
1236 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1239 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1240 if(sbt_field_size[i] < f)
1241 sbt_field_size[i] = f;
1243 sbt_field_title_condense_factor[i] = 0;
1244 if (sbt_field_title_width[i] > sbt_field_size[i])
1246 float real_maxwidth = sbt_field_size[i];
1247 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1248 if (sbt_field_title_width[i] > title_maxwidth)
1249 real_maxwidth = max(sbt_field_size[i], title_maxwidth);
1250 sbt_field_title_condense_factor[i] = real_maxwidth / sbt_field_title_width[i];
1256 void Scoreboard_initFieldSizes(bool compress_more)
1260 sbt_field_title_maxwidth_factor -= 0.05;
1261 if (sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor < 0.01 * vid_conwidth)
1263 sbt_field_title_maxwidth_factor = (0.01 * vid_conwidth) / sbt_field_title_maxwidth;
1268 sbt_field_title_maxwidth_factor = 1;
1270 for(int i = 0; i < sbt_num_fields; ++i)
1272 if (sbt_field[i] == SP_NAME)
1278 Scoreboard_FixColumnWidth(i, "", true);
1281 // update name field size in the end as it takes remaining space
1282 Scoreboard_FixColumnWidth(name_index, "", true);
1285 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1288 vector column_dim = eY * panel_size.y;
1290 column_dim.y -= 1.25 * hud_fontsize.y;
1291 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1292 pos.x += hud_fontsize.x * 0.5;
1293 for(i = 0; i < sbt_num_fields; ++i)
1295 if(sbt_field[i] == SP_SEPARATOR)
1297 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1300 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1301 vector prev_drawfontscale = drawfontscale;
1302 if (sbt_field_title_condense_factor[i])
1303 drawfontscale.x *= sbt_field_title_condense_factor[i];
1304 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1305 if (sbt_field_title_condense_factor[i])
1307 drawfontscale.x *= sbt_field_title_condense_factor[i];
1308 drawfontscale = prev_drawfontscale;
1310 pos.x += column_dim.x;
1313 float left_columns_end = pos.x - hud_fontsize.x;
1315 if(sbt_field[i] == SP_SEPARATOR)
1317 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1318 for(i = sbt_num_fields - 1; i > 0; --i)
1320 if(sbt_field[i] == SP_SEPARATOR)
1323 pos.x -= sbt_field_size[i];
1328 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1329 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1332 vector prev_drawfontscale = drawfontscale;
1333 float titlewidth = stringwidth(sbt_field_title[i], false, hud_fontsize);
1334 if (sbt_field_title_condense_factor[i])
1336 drawfontscale.x *= sbt_field_title_condense_factor[i];
1337 text_offset.x = sbt_field_size[i] - titlewidth * sbt_field_title_condense_factor[i];
1340 text_offset.x = sbt_field_size[i] - titlewidth;
1341 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1342 if (sbt_field_title_condense_factor[i])
1344 drawfontscale.x *= sbt_field_title_condense_factor[i];
1345 drawfontscale = prev_drawfontscale;
1347 pos.x -= hud_fontsize.x;
1350 float right_columns_start = pos.x + hud_fontsize.x * 0.5;
1351 if (left_columns_end > right_columns_start)
1352 Scoreboard_initFieldSizes(true);
1355 pos.x = panel_pos.x;
1356 pos.y += 1.25 * hud_fontsize.y;
1360 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1362 TC(bool, is_self); TC(int, pl_number);
1364 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1366 vector h_pos = item_pos;
1367 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1368 // alternated rows highlighting
1369 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1371 if (pl == scoreboard_selected_player)
1372 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1375 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1376 else if((sbt_highlight) && (!(pl_number % 2)))
1377 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1379 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1381 vector pos = item_pos;
1382 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1384 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1386 pos.x += hud_fontsize.x * 0.5;
1387 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1388 vector tmp = '0 0 0';
1390 PlayerScoreField field;
1391 for(i = 0; i < sbt_num_fields; ++i)
1393 field = sbt_field[i];
1394 if(field == SP_SEPARATOR)
1397 if(is_spec && field != SP_NAME && field != SP_PING) {
1398 pos.x += sbt_field_size[i] + hud_fontsize.x;
1401 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1402 str = Scoreboard_FixColumnWidth(i, str, false);
1404 pos.x += sbt_field_size[i] + hud_fontsize.x;
1406 if(field == SP_NAME) {
1407 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1408 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1410 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1411 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1414 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1415 if(sbt_field_icon0 != "")
1416 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1417 if(sbt_field_icon1 != "")
1418 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1419 if(sbt_field_icon2 != "")
1420 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1423 if(sbt_field[i] == SP_SEPARATOR)
1425 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1426 for(i = sbt_num_fields-1; i > 0; --i)
1428 field = sbt_field[i];
1429 if(field == SP_SEPARATOR)
1432 if(is_spec && field != SP_NAME && field != SP_PING) {
1433 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1437 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1438 str = Scoreboard_FixColumnWidth(i, str, false);
1440 if(field == SP_NAME) {
1441 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1442 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1444 tmp.x = sbt_fixcolumnwidth_len;
1445 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1448 tmp.x = sbt_field_size[i];
1449 if(sbt_field_icon0 != "")
1450 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1451 if(sbt_field_icon1 != "")
1452 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1453 if(sbt_field_icon2 != "")
1454 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1455 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1460 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1463 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1466 vector h_pos = item_pos;
1467 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1469 bool complete = (this_team == NUM_SPECTATOR);
1472 if((sbt_highlight) && (!(pl_number % 2)))
1473 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1475 vector pos = item_pos;
1476 pos.x += hud_fontsize.x * 0.5;
1477 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1479 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1481 width_limit -= stringwidth("...", false, hud_fontsize);
1482 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1483 static float max_name_width = 0;
1485 float fieldsize = 0;
1486 float min_fieldsize = 0;
1487 float fieldpadding = hud_fontsize.x * 0.25;
1488 if(this_team == NUM_SPECTATOR)
1490 if(autocvar_hud_panel_scoreboard_spectators_showping)
1491 min_fieldsize = stringwidth("999", false, hud_fontsize);
1493 else if(autocvar_hud_panel_scoreboard_others_showscore)
1494 min_fieldsize = stringwidth("99", false, hud_fontsize);
1495 for(i = 0; pl; pl = pl.sort_next)
1497 if(pl.team != this_team)
1499 if(pl == ignored_pl)
1503 if(this_team == NUM_SPECTATOR)
1505 if(autocvar_hud_panel_scoreboard_spectators_showping)
1506 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1508 else if(autocvar_hud_panel_scoreboard_others_showscore)
1509 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1511 string str = entcs_GetName(pl.sv_entnum);
1512 if (autocvar_hud_panel_scoreboard_playerid)
1513 str = Scoreboard_AddPlayerId(str, pl);
1514 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1515 float column_width = stringwidth(str, true, hud_fontsize);
1516 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1518 if(column_width > max_name_width)
1519 max_name_width = column_width;
1520 column_width = max_name_width;
1524 fieldsize = stringwidth(field, false, hud_fontsize);
1525 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1528 if(pos.x + column_width > width_limit)
1533 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1538 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1539 pos.y += hud_fontsize.y * 1.25;
1543 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1545 if (pl == scoreboard_selected_player)
1547 h_size.x = column_width + hud_fontsize.x * 0.25;
1548 h_size.y = hud_fontsize.y;
1549 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1553 vector name_pos = pos;
1554 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1555 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1556 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1559 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1560 h_size.y = hud_fontsize.y;
1561 vector field_pos = pos;
1562 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1563 field_pos.x += column_width - h_size.x;
1565 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1566 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1567 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1571 h_size.x = column_width + hud_fontsize.x * 0.25;
1572 h_size.y = hud_fontsize.y;
1573 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1575 pos.x += column_width;
1576 pos.x += hud_fontsize.x;
1578 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1581 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1583 int max_players = 999;
1584 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1586 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1589 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1590 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1591 height /= team_count;
1594 height -= panel_bg_padding * 2; // - padding
1595 max_players = floor(height / (hud_fontsize.y * 1.25));
1596 if(max_players <= 1)
1598 if(max_players == tm.team_size)
1603 entity me = playerslots[current_player];
1605 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1606 panel_size.y += panel_bg_padding * 2;
1608 vector scoreboard_selected_hl_pos = pos;
1609 vector scoreboard_selected_hl_size = '0 0 0';
1610 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1611 scoreboard_selected_hl_size.y = panel_size.y;
1615 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1616 if(panel.current_panel_bg != "0")
1617 end_pos.y += panel_bg_border * 2;
1619 if(panel_bg_padding)
1621 panel_pos += '1 1 0' * panel_bg_padding;
1622 panel_size -= '2 2 0' * panel_bg_padding;
1626 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1630 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1632 pos.y += 1.25 * hud_fontsize.y;
1635 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1637 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1640 // print header row and highlight columns
1641 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1643 // fill the table and draw the rows
1644 bool is_self = false;
1645 bool self_shown = false;
1647 for(pl = players.sort_next; pl; pl = pl.sort_next)
1649 if(pl.team != tm.team)
1651 if(i == max_players - 2 && pl != me)
1653 if(!self_shown && me.team == tm.team)
1655 Scoreboard_DrawItem(pos, rgb, me, true, i);
1657 pos.y += 1.25 * hud_fontsize.y;
1661 if(i >= max_players - 1)
1663 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1666 is_self = (pl.sv_entnum == current_player);
1667 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1670 pos.y += 1.25 * hud_fontsize.y;
1674 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1676 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1678 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1679 _alpha *= panel_fg_alpha;
1681 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1685 panel_size.x += panel_bg_padding * 2; // restore initial width
1689 bool Scoreboard_WouldDraw()
1691 if (scoreboard_ui_enabled)
1693 if (scoreboard_ui_disabling)
1695 if (scoreboard_fade_alpha == 0)
1696 HUD_Scoreboard_UI_Disable_Instantly();
1699 if (intermission && scoreboard_ui_enabled == 2)
1701 HUD_Scoreboard_UI_Disable_Instantly();
1706 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1708 else if (QuickMenu_IsOpened())
1710 else if (HUD_Radar_Clickable())
1712 else if (sb_showscores) // set by +showscores engine command
1714 else if (intermission == 1)
1716 else if (intermission == 2)
1718 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1719 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1723 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1728 float average_accuracy;
1729 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1731 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1733 WepSet weapons_stat = WepSet_GetFromStat();
1734 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1735 int disownedcnt = 0;
1737 FOREACH(Weapons, it != WEP_Null, {
1738 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1740 WepSet set = it.m_wepset;
1741 if(it.spawnflags & WEP_TYPE_OTHER)
1746 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1748 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1755 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1756 if (weapon_cnt <= 0) return pos;
1759 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1761 int columns = ceil(weapon_cnt / rows);
1763 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1764 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1765 float height = weapon_height + hud_fontsize.y;
1767 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);
1768 pos.y += 1.25 * hud_fontsize.y;
1769 if(panel.current_panel_bg != "0")
1770 pos.y += panel_bg_border;
1773 panel_size.y = height * rows;
1774 panel_size.y += panel_bg_padding * 2;
1776 float panel_bg_alpha_save = panel_bg_alpha;
1777 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1779 panel_bg_alpha = panel_bg_alpha_save;
1781 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1782 if(panel.current_panel_bg != "0")
1783 end_pos.y += panel_bg_border * 2;
1785 if(panel_bg_padding)
1787 panel_pos += '1 1 0' * panel_bg_padding;
1788 panel_size -= '2 2 0' * panel_bg_padding;
1792 vector tmp = panel_size;
1794 float weapon_width = tmp.x / columns / rows;
1797 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1801 // column highlighting
1802 for (int i = 0; i < columns; ++i)
1804 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);
1807 for (int i = 0; i < rows; ++i)
1808 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1811 average_accuracy = 0;
1812 int weapons_with_stats = 0;
1814 pos.x += weapon_width / 2;
1816 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1819 Accuracy_LoadColors();
1821 float oldposx = pos.x;
1825 FOREACH(Weapons, it != WEP_Null, {
1826 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1828 WepSet set = it.m_wepset;
1829 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1831 if (it.spawnflags & WEP_TYPE_OTHER)
1835 if (weapon_stats >= 0)
1836 weapon_alpha = sbt_fg_alpha;
1838 weapon_alpha = 0.2 * sbt_fg_alpha;
1841 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1843 if (weapon_stats >= 0) {
1844 weapons_with_stats += 1;
1845 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1847 string s = sprintf("%d%%", weapon_stats * 100);
1848 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1850 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1851 rgb = Accuracy_GetColor(weapon_stats);
1853 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1855 tmpos.x += weapon_width * rows;
1856 pos.x += weapon_width * rows;
1857 if (rows == 2 && column == columns - 1) {
1865 if (weapons_with_stats)
1866 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1868 panel_size.x += panel_bg_padding * 2; // restore initial width
1873 bool is_item_filtered(entity it)
1875 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1877 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1880 if (it.instanceOfArmor || it.instanceOfHealth)
1882 int ha_mask = floor(mask) % 10;
1885 default: return false;
1886 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1887 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1888 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1889 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1892 if (it.instanceOfAmmo)
1894 int ammo_mask = floor(mask / 10) % 10;
1895 return (ammo_mask == 1);
1900 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1902 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1904 int disowned_cnt = 0;
1905 int uninteresting_cnt = 0;
1906 IL_EACH(default_order_items, true, {
1907 int q = g_inventory.inv_items[it.m_id];
1908 //q = 1; // debug: display all items
1909 if (is_item_filtered(it))
1910 ++uninteresting_cnt;
1914 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1915 int n = items_cnt - disowned_cnt;
1916 if (n <= 0) return pos;
1918 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1919 int columns = max(6, ceil(n / rows));
1921 float item_height = hud_fontsize.y * 2.3;
1922 float height = item_height + hud_fontsize.y;
1924 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1925 pos.y += 1.25 * hud_fontsize.y;
1926 if(panel.current_panel_bg != "0")
1927 pos.y += panel_bg_border;
1930 panel_size.y = height * rows;
1931 panel_size.y += panel_bg_padding * 2;
1933 float panel_bg_alpha_save = panel_bg_alpha;
1934 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1936 panel_bg_alpha = panel_bg_alpha_save;
1938 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1939 if(panel.current_panel_bg != "0")
1940 end_pos.y += panel_bg_border * 2;
1942 if(panel_bg_padding)
1944 panel_pos += '1 1 0' * panel_bg_padding;
1945 panel_size -= '2 2 0' * panel_bg_padding;
1949 vector tmp = panel_size;
1951 float item_width = tmp.x / columns / rows;
1954 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1958 // column highlighting
1959 for (int i = 0; i < columns; ++i)
1961 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);
1964 for (int i = 0; i < rows; ++i)
1965 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1969 pos.x += item_width / 2;
1971 float oldposx = pos.x;
1975 IL_EACH(default_order_items, !is_item_filtered(it), {
1976 int n = g_inventory.inv_items[it.m_id];
1977 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1978 if (n <= 0) continue;
1979 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);
1981 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1982 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1983 tmpos.x += item_width * rows;
1984 pos.x += item_width * rows;
1985 if (rows == 2 && column == columns - 1) {
1993 panel_size.x += panel_bg_padding * 2; // restore initial width
1998 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
2000 pos.x += hud_fontsize.x * 0.25;
2001 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2002 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
2003 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2005 pos.y += hud_fontsize.y;
2010 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
2011 float stat_secrets_found, stat_secrets_total;
2012 float stat_monsters_killed, stat_monsters_total;
2016 // get monster stats
2017 stat_monsters_killed = STAT(MONSTERS_KILLED);
2018 stat_monsters_total = STAT(MONSTERS_TOTAL);
2020 // get secrets stats
2021 stat_secrets_found = STAT(SECRETS_FOUND);
2022 stat_secrets_total = STAT(SECRETS_TOTAL);
2024 // get number of rows
2025 if(stat_secrets_total)
2027 if(stat_monsters_total)
2030 // if no rows, return
2034 // draw table header
2035 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2036 pos.y += 1.25 * hud_fontsize.y;
2037 if(panel.current_panel_bg != "0")
2038 pos.y += panel_bg_border;
2041 panel_size.y = hud_fontsize.y * rows;
2042 panel_size.y += panel_bg_padding * 2;
2045 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2046 if(panel.current_panel_bg != "0")
2047 end_pos.y += panel_bg_border * 2;
2049 if(panel_bg_padding)
2051 panel_pos += '1 1 0' * panel_bg_padding;
2052 panel_size -= '2 2 0' * panel_bg_padding;
2056 vector tmp = panel_size;
2059 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2062 if(stat_monsters_total)
2064 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2065 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2069 if(stat_secrets_total)
2071 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2072 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2075 panel_size.x += panel_bg_padding * 2; // restore initial width
2079 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2082 RANKINGS_RECEIVED_CNT = 0;
2083 for (i=RANKINGS_CNT-1; i>=0; --i)
2085 ++RANKINGS_RECEIVED_CNT;
2087 if (RANKINGS_RECEIVED_CNT == 0)
2090 vector hl_rgb = rgb + '0.5 0.5 0.5';
2092 vector scoreboard_selected_hl_pos = pos;
2094 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2095 pos.y += 1.25 * hud_fontsize.y;
2096 if(panel.current_panel_bg != "0")
2097 pos.y += panel_bg_border;
2099 vector scoreboard_selected_hl_size = '0 0 0';
2100 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2101 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2106 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2108 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2113 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2115 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2119 float ranksize = 3 * hud_fontsize.x;
2120 float timesize = 5 * hud_fontsize.x;
2121 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2122 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2123 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2126 rankings_cnt = RANKINGS_RECEIVED_CNT;
2127 rankings_rows = ceil(rankings_cnt / rankings_columns);
2130 // expand name column to fill the entire row
2131 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2132 namesize += available_space;
2133 columnsize.x += available_space;
2135 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2136 panel_size.y += panel_bg_padding * 2;
2137 scoreboard_selected_hl_size.y += panel_size.y;
2141 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2142 if(panel.current_panel_bg != "0")
2143 end_pos.y += panel_bg_border * 2;
2145 if(panel_bg_padding)
2147 panel_pos += '1 1 0' * panel_bg_padding;
2148 panel_size -= '2 2 0' * panel_bg_padding;
2154 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2156 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2158 int column = 0, j = 0;
2159 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2160 int start_item = rankings_start_column * rankings_rows;
2161 for(i = start_item; i < start_item + rankings_cnt; ++i)
2163 int t = grecordtime[i];
2167 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2168 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2169 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2170 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2172 str = count_ordinal(i+1);
2173 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2174 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2175 str = ColorTranslateRGB(grecordholder[i]);
2177 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2178 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2180 pos.y += 1.25 * hud_fontsize.y;
2182 if(j >= rankings_rows)
2186 pos.x += panel_size.x / rankings_columns;
2187 pos.y = panel_pos.y;
2190 strfree(zoned_name_self);
2192 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2194 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2195 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2198 panel_size.x += panel_bg_padding * 2; // restore initial width
2202 bool have_weapon_stats;
2203 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2205 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2207 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2210 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2211 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2217 if (!have_weapon_stats)
2219 FOREACH(Weapons, it != WEP_Null, {
2220 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2221 if (weapon_stats >= 0)
2223 have_weapon_stats = true;
2227 if (!have_weapon_stats)
2234 bool have_item_stats;
2235 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2237 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2239 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2242 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2243 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2249 if (!have_item_stats)
2251 IL_EACH(default_order_items, true, {
2252 if (!is_item_filtered(it))
2254 int q = g_inventory.inv_items[it.m_id];
2255 //q = 1; // debug: display all items
2258 have_item_stats = true;
2263 if (!have_item_stats)
2270 vector Scoreboard_Spectators_Draw(vector pos) {
2275 for(pl = players.sort_next; pl; pl = pl.sort_next)
2277 if(pl.team == NUM_SPECTATOR)
2279 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2280 if(tm.team == NUM_SPECTATOR)
2282 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2283 draw_beginBoldFont();
2284 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2286 pos.y += 1.25 * hud_fontsize.y;
2288 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2289 pos.y += 1.25 * hud_fontsize.y;
2294 if (str != "") // if there's at least one spectator
2295 pos.y += 0.5 * hud_fontsize.y;
2300 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2302 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2303 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2304 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2305 (s_label == "score") ? CTX(_("SCO^points")) :
2306 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2309 void Scoreboard_Draw()
2311 bool sb_init_field_sizes = false;
2313 if(!autocvar__hud_configure)
2315 if(!hud_draw_maximized) return;
2317 // frametime checks allow to toggle the scoreboard even when the game is paused
2318 if(scoreboard_active) {
2319 if (scoreboard_fade_alpha == 0)
2320 scoreboard_time = time;
2321 if(hud_configure_menu_open == 1)
2322 scoreboard_fade_alpha = 1;
2323 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2324 if (scoreboard_fadeinspeed && frametime)
2325 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2327 scoreboard_fade_alpha = 1;
2329 static string hud_fontsize_str;
2330 if(hud_fontsize_str != autocvar_hud_fontsize)
2332 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2333 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2334 sb_init_field_sizes = true;
2337 static float scoreboard_table_fieldtitle_maxwidth_prev;
2338 if (scoreboard_table_fieldtitle_maxwidth_prev != autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth)
2340 scoreboard_table_fieldtitle_maxwidth_prev = autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth;
2341 sbt_field_title_maxwidth = bound(0.01, autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth, 0.1);
2342 sbt_field_title_maxwidth *= vid_conwidth;
2343 sb_init_field_sizes = true;
2347 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2348 if (scoreboard_fadeoutspeed && frametime)
2349 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2351 scoreboard_fade_alpha = 0;
2354 if (!scoreboard_fade_alpha)
2356 scoreboard_acc_fade_alpha = 0;
2357 scoreboard_itemstats_fade_alpha = 0;
2362 scoreboard_fade_alpha = 0;
2364 if (autocvar_hud_panel_scoreboard_dynamichud)
2367 HUD_Scale_Disable();
2369 if(scoreboard_fade_alpha <= 0)
2371 panel_fade_alpha *= scoreboard_fade_alpha;
2372 HUD_Panel_LoadCvars();
2374 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2375 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2376 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2377 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2378 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2379 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2380 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2382 // don't overlap with con_notify
2383 if(!autocvar__hud_configure)
2384 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2386 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2387 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2388 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2389 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2390 panel_pos.x = scoreboard_left;
2391 panel_size.x = fixed_scoreboard_width;
2393 // field sizes can be initialized now after panel_size.x calculation
2394 if (!sbt_field_size[0] || sb_init_field_sizes)
2395 Scoreboard_initFieldSizes(false);
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 // draw scoreboard spectators before accuracy and item stats
2608 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2609 pos = Scoreboard_Spectators_Draw(pos);
2612 // draw accuracy and item stats
2613 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2614 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2615 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2616 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2618 // draw scoreboard spectators after accuracy and item stats and before rankings
2619 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2620 pos = Scoreboard_Spectators_Draw(pos);
2623 if(MUTATOR_CALLHOOK(ShowRankings)) {
2624 string ranktitle = M_ARGV(0, string);
2625 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2626 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2627 if(race_speedaward_alltimebest)
2630 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2634 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2635 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2636 str = strcat(str, " / ");
2638 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2639 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2640 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2641 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2643 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2648 // draw scoreboard spectators after rankings
2649 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2650 pos = Scoreboard_Spectators_Draw(pos);
2653 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2655 // draw scoreboard spectators after mapstats
2656 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2657 pos = Scoreboard_Spectators_Draw(pos);
2661 // print information about respawn status
2662 float respawn_time = STAT(RESPAWN_TIME);
2663 if(!intermission && respawn_time)
2665 if(respawn_time < 0)
2667 // a negative number means we are awaiting respawn, time value is still the same
2668 respawn_time *= -1; // remove mark now that we checked it
2670 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2671 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2673 str = sprintf(_("^1Respawning in ^3%s^1..."),
2674 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2675 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2677 count_seconds(ceil(respawn_time - time))
2681 else if(time < respawn_time)
2683 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2684 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2685 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2687 count_seconds(ceil(respawn_time - time))
2691 else if(time >= respawn_time)
2692 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2694 pos.y += 1.2 * hud_fontsize.y;
2695 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2698 pos.y += hud_fontsize.y;
2699 if (scoreboard_fade_alpha < 1)
2700 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2701 else if (pos.y != scoreboard_bottom)
2703 if (pos.y > scoreboard_bottom)
2704 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2706 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2711 if (scoreboard_fade_alpha == 1)
2713 if (scoreboard_bottom > 0.95 * vid_conheight)
2714 rankings_rows = max(1, rankings_rows - 1);
2715 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2716 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2718 rankings_cnt = rankings_rows * rankings_columns;