1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
21 void Scoreboard_Draw_Export(int fh)
23 // allow saving cvars that aesthetically change the panel into hud skin files
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
111 float scoreboard_time;
113 // mode 0: returns translated label
114 // mode 1: prints name and description of all the labels
115 string Label_getInfo(string label, int mode)
118 label = "bckills"; // first case in the switch
122 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
123 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
124 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_HELP(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
125 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
126 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
127 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
128 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
129 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
130 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
131 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
132 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
133 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
134 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
135 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
136 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
137 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
138 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
139 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
140 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
141 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
142 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
143 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
144 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
145 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
146 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
147 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
148 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
149 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_HELP(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
150 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
151 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
152 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
153 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
154 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
155 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
156 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
157 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
158 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
159 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
160 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
161 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
162 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
163 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
164 default: return label;
168 int rankings_start_column;
169 int rankings_rows = 0;
170 int rankings_columns = 0;
171 int rankings_cnt = 0;
172 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
178 mousepos.x = nPrimary;
179 mousepos.y = nSecondary;
186 // at this point bInputType can only be 0 or 1 (key pressed or released)
187 bool key_pressed = (bInputType == 0);
189 if(scoreboard_ui_enabled)
191 // ESC to exit (TAB-ESC works too)
192 if(nPrimary == K_ESCAPE)
196 scoreboard_showscores = false;
197 scoreboard_ui_enabled = false;
198 scoreboard_selected_panel = 0;
199 scoreboard_selected_player = NULL;
203 else //if(!scoreboard_ui_enabled)
206 if(nPrimary == K_ESCAPE && (hudShiftState & S_TAB))
211 scoreboard_ui_enabled = true;
212 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
213 scoreboard_selected_panel_time = time;
217 // block any input while a menu dialog is fading
218 if(autocvar__menu_alpha)
224 // allow console bind to work
225 string con_keys = findkeysforcommand("toggleconsole", 0);
226 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
228 bool hit_con_bind = false;
230 for (i = 0; i < keys; ++i)
232 if(nPrimary == stof(argv(i)))
237 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
238 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
239 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
240 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
243 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
244 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
245 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
246 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
249 if(!scoreboard_ui_enabled)
252 if(nPrimary == K_TAB)
256 ++scoreboard_selected_panel;
257 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
258 ++scoreboard_selected_panel;
259 if (scoreboard_selected_panel >= SB_PANEL_MAX)
260 scoreboard_selected_panel = 1;
262 scoreboard_selected_panel_time = time;
264 else if(nPrimary == K_DOWNARROW)
268 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
271 entity curr_pl = NULL;
272 bool scoreboard_selected_player_found = false;
273 if (!scoreboard_selected_player)
274 scoreboard_selected_player_found = true;
276 for(tm = teams.sort_next; tm; tm = tm.sort_next)
278 if(tm.team != NUM_SPECTATOR)
279 for(pl = players.sort_next; pl; pl = pl.sort_next)
281 if(pl.team != tm.team)
284 if (scoreboard_selected_player_found)
286 if (scoreboard_selected_player == pl)
287 scoreboard_selected_player_found = true;
291 if (curr_pl == scoreboard_selected_player) // loop reached the last player
293 scoreboard_selected_player = curr_pl;
296 else if(nPrimary == K_UPARROW)
300 entity prev_pl = NULL;
301 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
304 for(tm = teams.sort_next; tm; tm = tm.sort_next)
306 if(tm.team != NUM_SPECTATOR)
307 for(pl = players.sort_next; pl; pl = pl.sort_next)
309 if(pl.team != tm.team)
311 if (pl == scoreboard_selected_player)
317 scoreboard_selected_player = prev_pl;
320 else if(nPrimary == K_RIGHTARROW)
324 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
325 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
327 else if(nPrimary == K_LEFTARROW)
331 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
332 rankings_start_column = max(rankings_start_column - 1, 0);
334 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
338 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
340 if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
343 scoreboard_ui_enabled = false;
344 scoreboard_showscores = false;
345 scoreboard_selected_panel = 0;
346 scoreboard_selected_player = NULL;
349 localcmd(sprintf("spectate %d", scoreboard_selected_player.sv_entnum + 1));
352 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
356 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
358 if (scoreboard_selected_player)
360 localcmd(sprintf("commandmode tell \"%s^7\"", entcs_GetName(scoreboard_selected_player.sv_entnum)));
361 scoreboard_ui_enabled = false;
362 scoreboard_showscores = false;
363 scoreboard_selected_panel = 0;
364 scoreboard_selected_player = NULL;
368 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
372 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
374 if (scoreboard_selected_player)
375 localcmd(sprintf("vcall kick \"%s^7\"", entcs_GetName(scoreboard_selected_player.sv_entnum)));
378 else if(hit_con_bind || nPrimary == K_PAUSE)
384 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
385 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
387 #define SB_EXTRA_SORTING_FIELDS 5
388 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
389 void Scoreboard_InitScores()
393 ps_primary = ps_secondary = NULL;
394 ts_primary = ts_secondary = -1;
395 FOREACH(Scores, true, {
396 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
397 if(f == SFL_SORT_PRIO_PRIMARY)
399 if(f == SFL_SORT_PRIO_SECONDARY)
401 if(ps_primary == it || ps_secondary == it)
403 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
404 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
405 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
406 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
407 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
409 if(ps_secondary == NULL)
410 ps_secondary = ps_primary;
412 for(i = 0; i < MAX_TEAMSCORE; ++i)
414 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
415 if(f == SFL_SORT_PRIO_PRIMARY)
417 if(f == SFL_SORT_PRIO_SECONDARY)
420 if(ts_secondary == -1)
421 ts_secondary = ts_primary;
423 Cmd_Scoreboard_SetFields(0);
427 void Scoreboard_UpdatePlayerTeams()
431 for(pl = players.sort_next; pl; pl = pl.sort_next)
434 int Team = entcs_GetScoreTeam(pl.sv_entnum);
435 if(SetTeam(pl, Team))
438 Scoreboard_UpdatePlayerPos(pl);
442 pl = players.sort_next;
447 print(strcat("PNUM: ", ftos(num), "\n"));
452 int Scoreboard_CompareScore(int vl, int vr, int f)
454 TC(int, vl); TC(int, vr); TC(int, f);
455 if(f & SFL_ZERO_IS_WORST)
457 if(vl == 0 && vr != 0)
459 if(vl != 0 && vr == 0)
463 return IS_INCREASING(f);
465 return IS_DECREASING(f);
469 float Scoreboard_ComparePlayerScores(entity left, entity right)
471 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
472 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
479 if(vl == NUM_SPECTATOR)
481 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
483 if(!left.gotscores && right.gotscores)
490 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
494 if (!fld) fld = ps_primary;
495 else if (ps_secondary == ps_primary) continue;
496 else fld = ps_secondary;
500 fld = sb_extra_sorting_field[i];
501 if (fld == ps_primary || fld == ps_secondary) continue;
505 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
506 if (r >= 0) return r;
509 if (left.sv_entnum < right.sv_entnum)
515 void Scoreboard_UpdatePlayerPos(entity player)
518 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
520 SORT_SWAP(player, ent);
522 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
524 SORT_SWAP(ent, player);
528 float Scoreboard_CompareTeamScores(entity left, entity right)
530 if(left.team == NUM_SPECTATOR)
532 if(right.team == NUM_SPECTATOR)
537 for(int i = -2; i < MAX_TEAMSCORE; ++i)
541 if (fld_idx == -1) fld_idx = ts_primary;
542 else if (ts_secondary == ts_primary) continue;
543 else fld_idx = ts_secondary;
548 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
551 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
552 if (r >= 0) return r;
555 if (left.team < right.team)
561 void Scoreboard_UpdateTeamPos(entity Team)
564 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
566 SORT_SWAP(Team, ent);
568 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
570 SORT_SWAP(ent, Team);
574 void Cmd_Scoreboard_Help()
576 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
577 LOG_HELP(_("Usage:"));
578 LOG_HELP("^2scoreboard_columns_set ^3default");
579 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
580 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
581 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
582 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
583 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
584 LOG_HELP(_("The following field names are recognized (case insensitive):"));
590 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
591 "of game types, then a slash, to make the field show up only in these\n"
592 "or in all but these game types. You can also specify 'all' as a\n"
593 "field to show all fields available for the current game mode."));
596 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
597 "include/exclude ALL teams/noteams game modes."));
600 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
601 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
602 "right of the vertical bar aligned to the right."));
603 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
604 "other gamemodes except DM."));
607 // NOTE: adding a gametype with ? to not warn for an optional field
608 // make sure it's excluded in a previous exclusive rule, if any
609 // otherwise the previous exclusive rule warns anyway
610 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
611 #define SCOREBOARD_DEFAULT_COLUMNS \
612 "ping pl fps name |" \
613 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
614 " -teams,lms/deaths +ft,tdm/deaths" \
616 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
617 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
618 " +tdm,ft,dom,ons,as/teamkills"\
619 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
620 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
621 " +lms/lives +lms/rank" \
622 " +kh/kckills +kh/losses +kh/caps" \
623 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
624 " +as/objectives +nb/faults +nb/goals" \
625 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
626 " +dom/ticks +dom/takes" \
627 " -lms,rc,cts,inv,nb/score"
629 void Cmd_Scoreboard_SetFields(int argc)
634 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
638 return; // do nothing, we don't know gametype and scores yet
640 // sbt_fields uses strunzone on the titles!
641 if(!sbt_field_title[0])
642 for(i = 0; i < MAX_SBT_FIELDS; ++i)
643 sbt_field_title[i] = strzone("(null)");
645 // TODO: re enable with gametype dependant cvars?
646 if(argc < 3) // no arguments provided
647 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
650 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
654 if(argv(2) == "default" || argv(2) == "expand_default")
656 if(argv(2) == "expand_default")
657 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
658 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
660 else if(argv(2) == "all" || argv(2) == "ALL")
662 string s = "ping pl name |"; // scores without label (not really scores)
665 // scores without label
666 s = strcat(s, " ", "sum");
667 s = strcat(s, " ", "kdratio");
668 s = strcat(s, " ", "frags");
670 FOREACH(Scores, true, {
672 if(it != ps_secondary)
673 if(scores_label(it) != "")
674 s = strcat(s, " ", scores_label(it));
676 if(ps_secondary != ps_primary)
677 s = strcat(s, " ", scores_label(ps_secondary));
678 s = strcat(s, " ", scores_label(ps_primary));
679 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
686 hud_fontsize = HUD_GetFontsize("hud_fontsize");
688 for(i = 1; i < argc - 1; ++i)
691 bool nocomplain = false;
692 if(substring(str, 0, 1) == "?")
695 str = substring(str, 1, strlen(str) - 1);
698 slash = strstrofs(str, "/", 0);
701 pattern = substring(str, 0, slash);
702 str = substring(str, slash + 1, strlen(str) - (slash + 1));
704 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
708 str = strtolower(str);
709 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
710 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
715 // fields without a label (not networked via the score system)
716 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
717 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
718 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
719 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
720 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
721 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
722 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
723 default: // fields with a label
725 // map alternative labels
726 if (str == "damage") str = "dmg";
727 if (str == "damagetaken") str = "dmgtaken";
729 FOREACH(Scores, true, {
730 if (str == strtolower(scores_label(it))) {
732 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
736 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
737 if(!nocomplain && str != "fps") // server can disable the fps field
738 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
740 strfree(sbt_field_title[sbt_num_fields]);
741 sbt_field_size[sbt_num_fields] = 0;
745 sbt_field[sbt_num_fields] = j;
748 if(j == ps_secondary)
749 have_secondary = true;
754 if(sbt_num_fields >= MAX_SBT_FIELDS)
758 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
760 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
761 have_secondary = true;
762 if(ps_primary == ps_secondary)
763 have_secondary = true;
764 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
766 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
770 strfree(sbt_field_title[sbt_num_fields]);
771 for(i = sbt_num_fields; i > 0; --i)
773 sbt_field_title[i] = sbt_field_title[i-1];
774 sbt_field_size[i] = sbt_field_size[i-1];
775 sbt_field[i] = sbt_field[i-1];
777 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
778 sbt_field[0] = SP_NAME;
780 LOG_INFO("fixed missing field 'name'");
784 strfree(sbt_field_title[sbt_num_fields]);
785 for(i = sbt_num_fields; i > 1; --i)
787 sbt_field_title[i] = sbt_field_title[i-1];
788 sbt_field_size[i] = sbt_field_size[i-1];
789 sbt_field[i] = sbt_field[i-1];
791 sbt_field_title[1] = strzone("|");
792 sbt_field[1] = SP_SEPARATOR;
793 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
795 LOG_INFO("fixed missing field '|'");
798 else if(!have_separator)
800 strcpy(sbt_field_title[sbt_num_fields], "|");
801 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
802 sbt_field[sbt_num_fields] = SP_SEPARATOR;
804 LOG_INFO("fixed missing field '|'");
808 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
809 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
810 sbt_field[sbt_num_fields] = ps_secondary;
812 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
816 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
817 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
818 sbt_field[sbt_num_fields] = ps_primary;
820 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
824 sbt_field[sbt_num_fields] = SP_END;
827 string Scoreboard_AddPlayerId(string pl_name, entity pl)
829 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
830 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
831 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
835 vector sbt_field_rgb;
836 string sbt_field_icon0;
837 string sbt_field_icon1;
838 string sbt_field_icon2;
839 vector sbt_field_icon0_rgb;
840 vector sbt_field_icon1_rgb;
841 vector sbt_field_icon2_rgb;
842 string Scoreboard_GetName(entity pl)
844 if(ready_waiting && pl.ready)
846 sbt_field_icon0 = "gfx/scoreboard/player_ready";
850 int f = entcs_GetClientColors(pl.sv_entnum);
852 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
853 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
854 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
855 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
856 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
859 return entcs_GetName(pl.sv_entnum);
862 string Scoreboard_GetField(entity pl, PlayerScoreField field)
864 float tmp, num, denom;
867 sbt_field_rgb = '1 1 1';
868 sbt_field_icon0 = "";
869 sbt_field_icon1 = "";
870 sbt_field_icon2 = "";
871 sbt_field_icon0_rgb = '1 1 1';
872 sbt_field_icon1_rgb = '1 1 1';
873 sbt_field_icon2_rgb = '1 1 1';
878 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
879 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
883 tmp = max(0, min(220, f-80)) / 220;
884 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
890 f = pl.ping_packetloss;
891 tmp = pl.ping_movementloss;
892 if(f == 0 && tmp == 0)
894 str = ftos(ceil(f * 100));
896 str = strcat(str, "~", ftos(ceil(tmp * 100)));
897 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
898 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
902 str = Scoreboard_GetName(pl);
903 if (autocvar_hud_panel_scoreboard_playerid)
904 str = Scoreboard_AddPlayerId(str, pl);
908 f = pl.(scores(SP_KILLS));
909 f -= pl.(scores(SP_SUICIDES));
913 num = pl.(scores(SP_KILLS));
914 denom = pl.(scores(SP_DEATHS));
917 sbt_field_rgb = '0 1 0';
918 str = sprintf("%d", num);
919 } else if(num <= 0) {
920 sbt_field_rgb = '1 0 0';
921 str = sprintf("%.1f", num/denom);
923 str = sprintf("%.1f", num/denom);
927 f = pl.(scores(SP_KILLS));
928 f -= pl.(scores(SP_DEATHS));
931 sbt_field_rgb = '0 1 0';
933 sbt_field_rgb = '1 1 1';
935 sbt_field_rgb = '1 0 0';
941 float elo = pl.(scores(SP_ELO));
943 case -1: return "...";
944 case -2: return _("N/A");
945 default: return ftos(elo);
951 float fps = pl.(scores(SP_FPS));
954 sbt_field_rgb = '1 1 1';
955 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
957 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
958 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
962 case SP_DMG: case SP_DMGTAKEN:
963 return sprintf("%.1f k", pl.(scores(field)) / 1000);
965 default: case SP_SCORE:
966 tmp = pl.(scores(field));
967 f = scores_flags(field);
968 if(field == ps_primary)
969 sbt_field_rgb = '1 1 0';
970 else if(field == ps_secondary)
971 sbt_field_rgb = '0 1 1';
973 sbt_field_rgb = '1 1 1';
974 return ScoreString(f, tmp);
979 float sbt_fixcolumnwidth_len;
980 float sbt_fixcolumnwidth_iconlen;
981 float sbt_fixcolumnwidth_marginlen;
983 string Scoreboard_FixColumnWidth(int i, string str)
989 sbt_fixcolumnwidth_iconlen = 0;
991 if(sbt_field_icon0 != "")
993 sz = draw_getimagesize(sbt_field_icon0);
995 if(sbt_fixcolumnwidth_iconlen < f)
996 sbt_fixcolumnwidth_iconlen = f;
999 if(sbt_field_icon1 != "")
1001 sz = draw_getimagesize(sbt_field_icon1);
1003 if(sbt_fixcolumnwidth_iconlen < f)
1004 sbt_fixcolumnwidth_iconlen = f;
1007 if(sbt_field_icon2 != "")
1009 sz = draw_getimagesize(sbt_field_icon2);
1011 if(sbt_fixcolumnwidth_iconlen < f)
1012 sbt_fixcolumnwidth_iconlen = f;
1015 if(sbt_fixcolumnwidth_iconlen != 0)
1017 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1018 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1021 sbt_fixcolumnwidth_marginlen = 0;
1023 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1026 float remaining_space = 0;
1027 for(j = 0; j < sbt_num_fields; ++j)
1029 if (sbt_field[i] != SP_SEPARATOR)
1030 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1031 sbt_field_size[i] = panel_size.x - remaining_space;
1033 if (sbt_fixcolumnwidth_iconlen != 0)
1034 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1035 float namesize = panel_size.x - remaining_space;
1036 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1037 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1039 max_namesize = vid_conwidth - remaining_space;
1042 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1044 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1045 if(sbt_field_size[i] < f)
1046 sbt_field_size[i] = f;
1051 void Scoreboard_initFieldSizes()
1053 for(int i = 0; i < sbt_num_fields; ++i)
1055 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1056 Scoreboard_FixColumnWidth(i, "");
1060 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1063 vector column_dim = eY * panel_size.y;
1065 column_dim.y -= 1.25 * hud_fontsize.y;
1066 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1067 pos.x += hud_fontsize.x * 0.5;
1068 for(i = 0; i < sbt_num_fields; ++i)
1070 if(sbt_field[i] == SP_SEPARATOR)
1072 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1075 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1076 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1077 pos.x += column_dim.x;
1079 if(sbt_field[i] == SP_SEPARATOR)
1081 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1082 for(i = sbt_num_fields - 1; i > 0; --i)
1084 if(sbt_field[i] == SP_SEPARATOR)
1087 pos.x -= sbt_field_size[i];
1092 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1093 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1096 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1097 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1098 pos.x -= hud_fontsize.x;
1102 pos.x = panel_pos.x;
1103 pos.y += 1.25 * hud_fontsize.y;
1107 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1109 TC(bool, is_self); TC(int, pl_number);
1111 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1113 vector h_pos = item_pos;
1114 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1115 // alternated rows highlighting
1116 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1118 if (pl == scoreboard_selected_player)
1119 drawfill(h_pos, h_size, rgb, 0.44, DRAWFLAG_NORMAL);
1122 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1123 else if((sbt_highlight) && (!(pl_number % 2)))
1124 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1126 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1128 vector pos = item_pos;
1129 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1131 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1133 pos.x += hud_fontsize.x * 0.5;
1134 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1135 vector tmp = '0 0 0';
1137 PlayerScoreField field;
1138 for(i = 0; i < sbt_num_fields; ++i)
1140 field = sbt_field[i];
1141 if(field == SP_SEPARATOR)
1144 if(is_spec && field != SP_NAME && field != SP_PING) {
1145 pos.x += sbt_field_size[i] + hud_fontsize.x;
1148 str = Scoreboard_GetField(pl, field);
1149 str = Scoreboard_FixColumnWidth(i, str);
1151 pos.x += sbt_field_size[i] + hud_fontsize.x;
1153 if(field == SP_NAME) {
1154 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1155 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1157 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1158 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1161 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1162 if(sbt_field_icon0 != "")
1163 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1164 if(sbt_field_icon1 != "")
1165 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1166 if(sbt_field_icon2 != "")
1167 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1170 if(sbt_field[i] == SP_SEPARATOR)
1172 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1173 for(i = sbt_num_fields-1; i > 0; --i)
1175 field = sbt_field[i];
1176 if(field == SP_SEPARATOR)
1179 if(is_spec && field != SP_NAME && field != SP_PING) {
1180 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1184 str = Scoreboard_GetField(pl, field);
1185 str = Scoreboard_FixColumnWidth(i, str);
1187 if(field == SP_NAME) {
1188 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1189 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1191 tmp.x = sbt_fixcolumnwidth_len;
1192 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1195 tmp.x = sbt_field_size[i];
1196 if(sbt_field_icon0 != "")
1197 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1198 if(sbt_field_icon1 != "")
1199 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1200 if(sbt_field_icon2 != "")
1201 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1202 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1207 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1210 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1213 vector h_pos = item_pos;
1214 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1216 bool complete = (this_team == NUM_SPECTATOR);
1219 if((sbt_highlight) && (!(pl_number % 2)))
1220 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1222 vector pos = item_pos;
1223 pos.x += hud_fontsize.x * 0.5;
1224 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1226 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1228 width_limit -= stringwidth("...", false, hud_fontsize);
1229 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1230 static float max_name_width = 0;
1232 float fieldsize = 0;
1233 float min_fieldsize = 0;
1234 float fieldpadding = hud_fontsize.x * 0.25;
1235 if(this_team == NUM_SPECTATOR)
1237 if(autocvar_hud_panel_scoreboard_spectators_showping)
1238 min_fieldsize = stringwidth("999", false, hud_fontsize);
1240 else if(autocvar_hud_panel_scoreboard_others_showscore)
1241 min_fieldsize = stringwidth("99", false, hud_fontsize);
1242 for(i = 0; pl; pl = pl.sort_next)
1244 if(pl.team != this_team)
1246 if(pl == ignored_pl)
1250 if(this_team == NUM_SPECTATOR)
1252 if(autocvar_hud_panel_scoreboard_spectators_showping)
1253 field = Scoreboard_GetField(pl, SP_PING);
1255 else if(autocvar_hud_panel_scoreboard_others_showscore)
1256 field = Scoreboard_GetField(pl, SP_SCORE);
1258 string str = entcs_GetName(pl.sv_entnum);
1259 if (autocvar_hud_panel_scoreboard_playerid)
1260 str = Scoreboard_AddPlayerId(str, pl);
1261 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1262 float column_width = stringwidth(str, true, hud_fontsize);
1263 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1265 if(column_width > max_name_width)
1266 max_name_width = column_width;
1267 column_width = max_name_width;
1271 fieldsize = stringwidth(field, false, hud_fontsize);
1272 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1275 if(pos.x + column_width > width_limit)
1280 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1285 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1286 pos.y += hud_fontsize.y * 1.25;
1290 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1292 if (pl == scoreboard_selected_player)
1294 h_size.x = column_width + hud_fontsize.x * 0.25;
1295 h_size.y = hud_fontsize.y;
1296 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44, DRAWFLAG_NORMAL);
1300 vector name_pos = pos;
1301 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1302 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1303 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1306 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1307 h_size.y = hud_fontsize.y;
1308 vector field_pos = pos;
1309 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1310 field_pos.x += column_width - h_size.x;
1312 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1313 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1314 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1318 h_size.x = column_width + hud_fontsize.x * 0.25;
1319 h_size.y = hud_fontsize.y;
1320 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1322 pos.x += column_width;
1323 pos.x += hud_fontsize.x;
1325 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1328 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1330 int max_players = 999;
1331 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1333 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1336 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1337 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1338 height /= team_count;
1341 height -= panel_bg_padding * 2; // - padding
1342 max_players = floor(height / (hud_fontsize.y * 1.25));
1343 if(max_players <= 1)
1345 if(max_players == tm.team_size)
1350 entity me = playerslots[current_player];
1352 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1353 panel_size.y += panel_bg_padding * 2;
1355 vector scoreboard_selected_hl_pos = pos;
1356 vector scoreboard_selected_hl_size = '0 0 0';
1357 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1358 scoreboard_selected_hl_size.y = panel_size.y;
1362 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1363 if(panel.current_panel_bg != "0")
1364 end_pos.y += panel_bg_border * 2;
1366 if(panel_bg_padding)
1368 panel_pos += '1 1 0' * panel_bg_padding;
1369 panel_size -= '2 2 0' * panel_bg_padding;
1373 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1377 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1379 pos.y += 1.25 * hud_fontsize.y;
1382 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1384 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1387 // print header row and highlight columns
1388 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1390 // fill the table and draw the rows
1391 bool is_self = false;
1392 bool self_shown = false;
1394 for(pl = players.sort_next; pl; pl = pl.sort_next)
1396 if(pl.team != tm.team)
1398 if(i == max_players - 2 && pl != me)
1400 if(!self_shown && me.team == tm.team)
1402 Scoreboard_DrawItem(pos, rgb, me, true, i);
1404 pos.y += 1.25 * hud_fontsize.y;
1408 if(i >= max_players - 1)
1410 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1413 is_self = (pl.sv_entnum == current_player);
1414 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1417 pos.y += 1.25 * hud_fontsize.y;
1421 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1423 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1424 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.3, DRAWFLAG_NORMAL);
1427 panel_size.x += panel_bg_padding * 2; // restore initial width
1431 bool Scoreboard_WouldDraw()
1433 if (MUTATOR_CALLHOOK(DrawScoreboard))
1435 else if (QuickMenu_IsOpened())
1437 else if (HUD_Radar_Clickable())
1439 else if (scoreboard_showscores)
1441 else if (intermission == 1)
1443 else if (intermission == 2)
1445 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1446 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1450 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1455 float average_accuracy;
1456 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1458 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1460 WepSet weapons_stat = WepSet_GetFromStat();
1461 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1462 int disownedcnt = 0;
1464 FOREACH(Weapons, it != WEP_Null, {
1465 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1467 WepSet set = it.m_wepset;
1468 if(it.spawnflags & WEP_TYPE_OTHER)
1473 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1475 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1482 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1483 if (weapon_cnt <= 0) return pos;
1486 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1488 int columnns = ceil(weapon_cnt / rows);
1490 float weapon_height = 29;
1491 float height = hud_fontsize.y + weapon_height;
1493 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);
1494 pos.y += 1.25 * hud_fontsize.y;
1495 if(panel.current_panel_bg != "0")
1496 pos.y += panel_bg_border;
1499 panel_size.y = height * rows;
1500 panel_size.y += panel_bg_padding * 2;
1502 float panel_bg_alpha_save = panel_bg_alpha;
1503 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1505 panel_bg_alpha = panel_bg_alpha_save;
1507 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1508 if(panel.current_panel_bg != "0")
1509 end_pos.y += panel_bg_border * 2;
1511 if(panel_bg_padding)
1513 panel_pos += '1 1 0' * panel_bg_padding;
1514 panel_size -= '2 2 0' * panel_bg_padding;
1518 vector tmp = panel_size;
1520 float weapon_width = tmp.x / columnns / rows;
1523 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1527 // column highlighting
1528 for (int i = 0; i < columnns; ++i)
1530 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);
1533 for (int i = 0; i < rows; ++i)
1534 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1537 average_accuracy = 0;
1538 int weapons_with_stats = 0;
1540 pos.x += weapon_width / 2;
1542 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1545 Accuracy_LoadColors();
1547 float oldposx = pos.x;
1551 FOREACH(Weapons, it != WEP_Null, {
1552 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1554 WepSet set = it.m_wepset;
1555 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1557 if (it.spawnflags & WEP_TYPE_OTHER)
1561 if (weapon_stats >= 0)
1562 weapon_alpha = sbt_fg_alpha;
1564 weapon_alpha = 0.2 * sbt_fg_alpha;
1567 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1569 if (weapon_stats >= 0) {
1570 weapons_with_stats += 1;
1571 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1574 s = sprintf("%d%%", weapon_stats * 100);
1577 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1579 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1580 rgb = Accuracy_GetColor(weapon_stats);
1582 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1584 tmpos.x += weapon_width * rows;
1585 pos.x += weapon_width * rows;
1586 if (rows == 2 && column == columnns - 1) {
1594 if (weapons_with_stats)
1595 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1597 panel_size.x += panel_bg_padding * 2; // restore initial width
1602 bool is_item_filtered(entity it)
1604 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1606 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1609 if (it.instanceOfArmor || it.instanceOfHealth)
1611 int ha_mask = floor(mask) % 10;
1614 default: return false;
1615 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1616 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1617 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1618 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1621 if (it.instanceOfAmmo)
1623 int ammo_mask = floor(mask / 10) % 10;
1624 return (ammo_mask == 1);
1629 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1631 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1633 int disowned_cnt = 0;
1634 int uninteresting_cnt = 0;
1635 IL_EACH(default_order_items, true, {
1636 int q = g_inventory.inv_items[it.m_id];
1637 //q = 1; // debug: display all items
1638 if (is_item_filtered(it))
1639 ++uninteresting_cnt;
1643 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1644 int n = items_cnt - disowned_cnt;
1645 if (n <= 0) return pos;
1647 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1648 int columnns = max(6, ceil(n / rows));
1651 float fontsize = height * 1/3;
1652 float item_height = height * 2/3;
1654 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1655 pos.y += 1.25 * hud_fontsize.y;
1656 if(panel.current_panel_bg != "0")
1657 pos.y += panel_bg_border;
1660 panel_size.y = height * rows;
1661 panel_size.y += panel_bg_padding * 2;
1663 float panel_bg_alpha_save = panel_bg_alpha;
1664 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1666 panel_bg_alpha = panel_bg_alpha_save;
1668 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1669 if(panel.current_panel_bg != "0")
1670 end_pos.y += panel_bg_border * 2;
1672 if(panel_bg_padding)
1674 panel_pos += '1 1 0' * panel_bg_padding;
1675 panel_size -= '2 2 0' * panel_bg_padding;
1679 vector tmp = panel_size;
1681 float item_width = tmp.x / columnns / rows;
1684 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1688 // column highlighting
1689 for (int i = 0; i < columnns; ++i)
1691 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1694 for (int i = 0; i < rows; ++i)
1695 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1699 pos.x += item_width / 2;
1701 float oldposx = pos.x;
1705 IL_EACH(default_order_items, !is_item_filtered(it), {
1706 int n = g_inventory.inv_items[it.m_id];
1707 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1708 if (n <= 0) continue;
1709 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1711 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1712 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1713 tmpos.x += item_width * rows;
1714 pos.x += item_width * rows;
1715 if (rows == 2 && column == columnns - 1) {
1723 panel_size.x += panel_bg_padding * 2; // restore initial width
1728 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1730 pos.x += hud_fontsize.x * 0.25;
1731 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1732 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1733 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1735 pos.y += hud_fontsize.y;
1740 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1741 float stat_secrets_found, stat_secrets_total;
1742 float stat_monsters_killed, stat_monsters_total;
1746 // get monster stats
1747 stat_monsters_killed = STAT(MONSTERS_KILLED);
1748 stat_monsters_total = STAT(MONSTERS_TOTAL);
1750 // get secrets stats
1751 stat_secrets_found = STAT(SECRETS_FOUND);
1752 stat_secrets_total = STAT(SECRETS_TOTAL);
1754 // get number of rows
1755 if(stat_secrets_total)
1757 if(stat_monsters_total)
1760 // if no rows, return
1764 // draw table header
1765 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_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 = hud_fontsize.y * rows;
1772 panel_size.y += panel_bg_padding * 2;
1775 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1776 if(panel.current_panel_bg != "0")
1777 end_pos.y += panel_bg_border * 2;
1779 if(panel_bg_padding)
1781 panel_pos += '1 1 0' * panel_bg_padding;
1782 panel_size -= '2 2 0' * panel_bg_padding;
1786 vector tmp = panel_size;
1789 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1792 if(stat_monsters_total)
1794 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1795 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1799 if(stat_secrets_total)
1801 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1802 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1805 panel_size.x += panel_bg_padding * 2; // restore initial width
1809 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1812 RANKINGS_RECEIVED_CNT = 0;
1813 for (i=RANKINGS_CNT-1; i>=0; --i)
1815 ++RANKINGS_RECEIVED_CNT;
1817 if (RANKINGS_RECEIVED_CNT == 0)
1820 vector hl_rgb = rgb + '0.5 0.5 0.5';
1822 vector scoreboard_selected_hl_pos = pos;
1824 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1825 pos.y += 1.25 * hud_fontsize.y;
1826 if(panel.current_panel_bg != "0")
1827 pos.y += panel_bg_border;
1829 vector scoreboard_selected_hl_size = '0 0 0';
1830 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1831 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1836 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1838 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1843 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1845 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1849 float ranksize = 3 * hud_fontsize.x;
1850 float timesize = 5 * hud_fontsize.x;
1851 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1852 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1853 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1856 rankings_cnt = RANKINGS_RECEIVED_CNT;
1857 rankings_rows = ceil(rankings_cnt / rankings_columns);
1860 // expand name column to fill the entire row
1861 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1862 namesize += available_space;
1863 columnsize.x += available_space;
1865 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1866 panel_size.y += panel_bg_padding * 2;
1867 scoreboard_selected_hl_size.y += panel_size.y;
1871 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1872 if(panel.current_panel_bg != "0")
1873 end_pos.y += panel_bg_border * 2;
1875 if(panel_bg_padding)
1877 panel_pos += '1 1 0' * panel_bg_padding;
1878 panel_size -= '2 2 0' * panel_bg_padding;
1884 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1886 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1888 int column = 0, j = 0;
1889 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1890 int start_item = rankings_start_column * rankings_rows;
1891 for(i = start_item; i < start_item + rankings_cnt; ++i)
1893 int t = grecordtime[i];
1897 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1898 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1899 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
1900 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1902 str = count_ordinal(i+1);
1903 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1904 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1905 str = ColorTranslateRGB(grecordholder[i]);
1907 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1908 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1910 pos.y += 1.25 * hud_fontsize.y;
1912 if(j >= rankings_rows)
1916 pos.x += panel_size.x / rankings_columns;
1917 pos.y = panel_pos.y;
1920 strfree(zoned_name_self);
1922 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
1924 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1925 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
1928 panel_size.x += panel_bg_padding * 2; // restore initial width
1932 bool have_weapon_stats;
1933 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1935 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1937 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1940 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1941 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1947 if (!have_weapon_stats)
1949 FOREACH(Weapons, it != WEP_Null, {
1950 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1951 if (weapon_stats >= 0)
1953 have_weapon_stats = true;
1957 if (!have_weapon_stats)
1964 bool have_item_stats;
1965 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1967 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1969 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1972 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1973 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1979 if (!have_item_stats)
1981 IL_EACH(default_order_items, true, {
1982 if (!is_item_filtered(it))
1984 int q = g_inventory.inv_items[it.m_id];
1985 //q = 1; // debug: display all items
1988 have_item_stats = true;
1993 if (!have_item_stats)
2000 vector Scoreboard_Spectators_Draw(vector pos) {
2005 for(pl = players.sort_next; pl; pl = pl.sort_next)
2007 if(pl.team == NUM_SPECTATOR)
2009 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2010 if(tm.team == NUM_SPECTATOR)
2012 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2013 draw_beginBoldFont();
2014 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2016 pos.y += 1.25 * hud_fontsize.y;
2018 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2019 pos.y += 1.25 * hud_fontsize.y;
2024 if (str != "") // if there's at least one spectator
2025 pos.y += 0.5 * hud_fontsize.y;
2030 void Scoreboard_Draw()
2032 if(!autocvar__hud_configure)
2034 if(!hud_draw_maximized) return;
2036 // frametime checks allow to toggle the scoreboard even when the game is paused
2037 if(scoreboard_active) {
2038 if (scoreboard_fade_alpha == 0)
2039 scoreboard_time = time;
2040 if(hud_configure_menu_open == 1)
2041 scoreboard_fade_alpha = 1;
2042 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2043 if (scoreboard_fadeinspeed && frametime)
2044 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2046 scoreboard_fade_alpha = 1;
2047 if(hud_fontsize_str != autocvar_hud_fontsize)
2049 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2050 Scoreboard_initFieldSizes();
2051 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2055 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2056 if (scoreboard_fadeoutspeed && frametime)
2057 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2059 scoreboard_fade_alpha = 0;
2062 if (!scoreboard_fade_alpha)
2064 scoreboard_acc_fade_alpha = 0;
2065 scoreboard_itemstats_fade_alpha = 0;
2070 scoreboard_fade_alpha = 0;
2072 if (autocvar_hud_panel_scoreboard_dynamichud)
2075 HUD_Scale_Disable();
2077 if(scoreboard_fade_alpha <= 0)
2079 panel_fade_alpha *= scoreboard_fade_alpha;
2080 HUD_Panel_LoadCvars();
2082 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2083 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2084 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2085 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2086 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2087 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2088 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2090 // don't overlap with con_notify
2091 if(!autocvar__hud_configure)
2092 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2094 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2095 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2096 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2097 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2098 panel_pos.x = scoreboard_left;
2099 panel_size.x = fixed_scoreboard_width;
2101 Scoreboard_UpdatePlayerTeams();
2103 scoreboard_top = panel_pos.y;
2104 vector pos = panel_pos;
2109 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2111 // Begin of Game Info Section
2112 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2113 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2115 // Game Info: Game Type
2116 str = MapInfo_Type_ToText(gametype);
2117 draw_beginBoldFont();
2118 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);
2121 // Game Info: Game Detail
2122 float tl = STAT(TIMELIMIT);
2123 float fl = STAT(FRAGLIMIT);
2124 float ll = STAT(LEADLIMIT);
2125 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2128 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2129 if(!gametype.m_hidelimits)
2134 str = strcat(str, "^7 / "); // delimiter
2137 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2138 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2139 (teamscores_label(ts_primary) == "fastest") ? "" :
2140 TranslateScoresLabel(teamscores_label(ts_primary))));
2144 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2145 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2146 (scores_label(ps_primary) == "fastest") ? "" :
2147 TranslateScoresLabel(scores_label(ps_primary))));
2152 if(tl > 0 || fl > 0)
2155 if (ll_and_fl && fl > 0)
2156 str = strcat(str, "^7 & ");
2158 str = strcat(str, "^7 / ");
2163 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2164 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2165 (teamscores_label(ts_primary) == "fastest") ? "" :
2166 TranslateScoresLabel(teamscores_label(ts_primary))));
2170 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2171 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2172 (scores_label(ps_primary) == "fastest") ? "" :
2173 TranslateScoresLabel(scores_label(ps_primary))));
2178 pos.y += sb_gameinfo_type_fontsize.y;
2179 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
2181 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2182 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2183 // End of Game Info Section
2185 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2186 if(panel.current_panel_bg != "0")
2187 pos.y += panel_bg_border;
2189 // Draw the scoreboard
2190 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2193 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2197 vector panel_bg_color_save = panel_bg_color;
2198 vector team_score_baseoffset;
2199 vector team_size_baseoffset;
2200 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2202 // put team score to the left of scoreboard (and team size to the right)
2203 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2204 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2205 if(panel.current_panel_bg != "0")
2207 team_score_baseoffset.x -= panel_bg_border;
2208 team_size_baseoffset.x += panel_bg_border;
2213 // put team score to the right of scoreboard (and team size to the left)
2214 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2215 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2216 if(panel.current_panel_bg != "0")
2218 team_score_baseoffset.x += panel_bg_border;
2219 team_size_baseoffset.x -= panel_bg_border;
2223 int team_size_total = 0;
2224 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2226 // calculate team size total (sum of all team sizes)
2227 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2228 if(tm.team != NUM_SPECTATOR)
2229 team_size_total += tm.team_size;
2232 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2234 if(tm.team == NUM_SPECTATOR)
2239 draw_beginBoldFont();
2240 vector rgb = Team_ColorRGB(tm.team);
2241 str = ftos(tm.(teamscores(ts_primary)));
2242 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2244 // team score on the left (default)
2245 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2249 // team score on the right
2250 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2252 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2254 // team size (if set to show on the side)
2255 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2257 // calculate the starting position for the whole team size info string
2258 str = sprintf("%d/%d", tm.team_size, team_size_total);
2259 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2261 // team size on the left
2262 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2266 // team size on the right
2267 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2269 str = sprintf("%d", tm.team_size);
2270 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2271 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2272 str = sprintf("/%d", team_size_total);
2273 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2277 // secondary score, e.g. keyhunt
2278 if(ts_primary != ts_secondary)
2280 str = ftos(tm.(teamscores(ts_secondary)));
2281 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2284 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2289 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2292 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2295 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2296 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2297 else if(panel_bg_color_team > 0)
2298 panel_bg_color = rgb * panel_bg_color_team;
2300 panel_bg_color = rgb;
2301 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2303 panel_bg_color = panel_bg_color_save;
2307 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2308 if(tm.team != NUM_SPECTATOR)
2311 // display it anyway
2312 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2315 // draw scoreboard spectators before accuracy and item stats
2316 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2317 pos = Scoreboard_Spectators_Draw(pos);
2320 // draw accuracy and item stats
2321 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2322 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2323 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2324 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2326 // draw scoreboard spectators after accuracy and item stats and before rankings
2327 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2328 pos = Scoreboard_Spectators_Draw(pos);
2331 if(MUTATOR_CALLHOOK(ShowRankings)) {
2332 string ranktitle = M_ARGV(0, string);
2333 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2334 if(race_speedaward_alltimebest)
2337 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2341 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2342 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2343 str = strcat(str, " / ");
2345 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2346 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2347 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2348 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2350 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2355 // draw scoreboard spectators after rankings
2356 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2357 pos = Scoreboard_Spectators_Draw(pos);
2360 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2362 // draw scoreboard spectators after mapstats
2363 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2364 pos = Scoreboard_Spectators_Draw(pos);
2368 // print information about respawn status
2369 float respawn_time = STAT(RESPAWN_TIME);
2373 if(respawn_time < 0)
2375 // a negative number means we are awaiting respawn, time value is still the same
2376 respawn_time *= -1; // remove mark now that we checked it
2378 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2379 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2381 str = sprintf(_("^1Respawning in ^3%s^1..."),
2382 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2383 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2385 count_seconds(ceil(respawn_time - time))
2389 else if(time < respawn_time)
2391 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2392 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2393 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2395 count_seconds(ceil(respawn_time - time))
2399 else if(time >= respawn_time)
2400 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2402 pos.y += 1.2 * hud_fontsize.y;
2403 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2406 pos.y += hud_fontsize.y;
2407 if (scoreboard_fade_alpha < 1)
2408 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2409 else if (pos.y != scoreboard_bottom)
2411 if (pos.y > scoreboard_bottom)
2412 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2414 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2419 if (scoreboard_fade_alpha == 1)
2421 if (scoreboard_bottom > 0.95 * vid_conheight)
2422 rankings_rows = max(1, rankings_rows - 1);
2423 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2424 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2426 rankings_cnt = rankings_rows * rankings_columns;