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()
429 static float update_time;
430 if (time <= update_time)
436 for(pl = players.sort_next; pl; pl = pl.sort_next)
439 int Team = entcs_GetScoreTeam(pl.sv_entnum);
440 if(SetTeam(pl, Team))
443 Scoreboard_UpdatePlayerPos(pl);
447 pl = players.sort_next;
452 print(strcat("PNUM: ", ftos(num), "\n"));
457 int Scoreboard_CompareScore(int vl, int vr, int f)
459 TC(int, vl); TC(int, vr); TC(int, f);
460 if(f & SFL_ZERO_IS_WORST)
462 if(vl == 0 && vr != 0)
464 if(vl != 0 && vr == 0)
468 return IS_INCREASING(f);
470 return IS_DECREASING(f);
474 float Scoreboard_ComparePlayerScores(entity left, entity right)
476 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
477 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
484 if(vl == NUM_SPECTATOR)
486 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
488 if(!left.gotscores && right.gotscores)
495 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
499 if (!fld) fld = ps_primary;
500 else if (ps_secondary == ps_primary) continue;
501 else fld = ps_secondary;
505 fld = sb_extra_sorting_field[i];
506 if (fld == ps_primary || fld == ps_secondary) continue;
510 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
511 if (r >= 0) return r;
514 if (left.sv_entnum < right.sv_entnum)
520 void Scoreboard_UpdatePlayerPos(entity player)
523 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
525 SORT_SWAP(player, ent);
527 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
529 SORT_SWAP(ent, player);
533 float Scoreboard_CompareTeamScores(entity left, entity right)
535 if(left.team == NUM_SPECTATOR)
537 if(right.team == NUM_SPECTATOR)
542 for(int i = -2; i < MAX_TEAMSCORE; ++i)
546 if (fld_idx == -1) fld_idx = ts_primary;
547 else if (ts_secondary == ts_primary) continue;
548 else fld_idx = ts_secondary;
553 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
556 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
557 if (r >= 0) return r;
560 if (left.team < right.team)
566 void Scoreboard_UpdateTeamPos(entity Team)
569 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
571 SORT_SWAP(Team, ent);
573 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
575 SORT_SWAP(ent, Team);
579 void Cmd_Scoreboard_Help()
581 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
582 LOG_HELP(_("Usage:"));
583 LOG_HELP("^2scoreboard_columns_set ^3default");
584 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
585 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
586 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
587 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
588 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
589 LOG_HELP(_("The following field names are recognized (case insensitive):"));
595 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
596 "of game types, then a slash, to make the field show up only in these\n"
597 "or in all but these game types. You can also specify 'all' as a\n"
598 "field to show all fields available for the current game mode."));
601 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
602 "include/exclude ALL teams/noteams game modes."));
605 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
606 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
607 "right of the vertical bar aligned to the right."));
608 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
609 "other gamemodes except DM."));
612 // NOTE: adding a gametype with ? to not warn for an optional field
613 // make sure it's excluded in a previous exclusive rule, if any
614 // otherwise the previous exclusive rule warns anyway
615 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
616 #define SCOREBOARD_DEFAULT_COLUMNS \
617 "ping pl fps name |" \
618 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
619 " -teams,lms/deaths +ft,tdm/deaths" \
621 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
622 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
623 " +tdm,ft,dom,ons,as/teamkills"\
624 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
625 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
626 " +lms/lives +lms/rank" \
627 " +kh/kckills +kh/losses +kh/caps" \
628 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
629 " +as/objectives +nb/faults +nb/goals" \
630 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
631 " +dom/ticks +dom/takes" \
632 " -lms,rc,cts,inv,nb/score"
634 void Cmd_Scoreboard_SetFields(int argc)
639 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
643 return; // do nothing, we don't know gametype and scores yet
645 // sbt_fields uses strunzone on the titles!
646 if(!sbt_field_title[0])
647 for(i = 0; i < MAX_SBT_FIELDS; ++i)
648 sbt_field_title[i] = strzone("(null)");
650 // TODO: re enable with gametype dependant cvars?
651 if(argc < 3) // no arguments provided
652 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
655 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
659 if(argv(2) == "default" || argv(2) == "expand_default")
661 if(argv(2) == "expand_default")
662 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
663 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
665 else if(argv(2) == "all" || argv(2) == "ALL")
667 string s = "ping pl name |"; // scores without label (not really scores)
670 // scores without label
671 s = strcat(s, " ", "sum");
672 s = strcat(s, " ", "kdratio");
673 s = strcat(s, " ", "frags");
675 FOREACH(Scores, true, {
677 if(it != ps_secondary)
678 if(scores_label(it) != "")
679 s = strcat(s, " ", scores_label(it));
681 if(ps_secondary != ps_primary)
682 s = strcat(s, " ", scores_label(ps_secondary));
683 s = strcat(s, " ", scores_label(ps_primary));
684 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
691 hud_fontsize = HUD_GetFontsize("hud_fontsize");
693 for(i = 1; i < argc - 1; ++i)
696 bool nocomplain = false;
697 if(substring(str, 0, 1) == "?")
700 str = substring(str, 1, strlen(str) - 1);
703 slash = strstrofs(str, "/", 0);
706 pattern = substring(str, 0, slash);
707 str = substring(str, slash + 1, strlen(str) - (slash + 1));
709 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
713 str = strtolower(str);
714 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
715 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
720 // fields without a label (not networked via the score system)
721 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
722 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
723 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
724 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
725 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
726 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
727 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
728 default: // fields with a label
730 // map alternative labels
731 if (str == "damage") str = "dmg";
732 if (str == "damagetaken") str = "dmgtaken";
734 FOREACH(Scores, true, {
735 if (str == strtolower(scores_label(it))) {
737 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
741 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
742 if(!nocomplain && str != "fps") // server can disable the fps field
743 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
745 strfree(sbt_field_title[sbt_num_fields]);
746 sbt_field_size[sbt_num_fields] = 0;
750 sbt_field[sbt_num_fields] = j;
753 if(j == ps_secondary)
754 have_secondary = true;
759 if(sbt_num_fields >= MAX_SBT_FIELDS)
763 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
765 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
766 have_secondary = true;
767 if(ps_primary == ps_secondary)
768 have_secondary = true;
769 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
771 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
775 strfree(sbt_field_title[sbt_num_fields]);
776 for(i = sbt_num_fields; i > 0; --i)
778 sbt_field_title[i] = sbt_field_title[i-1];
779 sbt_field_size[i] = sbt_field_size[i-1];
780 sbt_field[i] = sbt_field[i-1];
782 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
783 sbt_field[0] = SP_NAME;
785 LOG_INFO("fixed missing field 'name'");
789 strfree(sbt_field_title[sbt_num_fields]);
790 for(i = sbt_num_fields; i > 1; --i)
792 sbt_field_title[i] = sbt_field_title[i-1];
793 sbt_field_size[i] = sbt_field_size[i-1];
794 sbt_field[i] = sbt_field[i-1];
796 sbt_field_title[1] = strzone("|");
797 sbt_field[1] = SP_SEPARATOR;
798 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
800 LOG_INFO("fixed missing field '|'");
803 else if(!have_separator)
805 strcpy(sbt_field_title[sbt_num_fields], "|");
806 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
807 sbt_field[sbt_num_fields] = SP_SEPARATOR;
809 LOG_INFO("fixed missing field '|'");
813 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
814 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
815 sbt_field[sbt_num_fields] = ps_secondary;
817 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
821 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
822 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
823 sbt_field[sbt_num_fields] = ps_primary;
825 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
829 sbt_field[sbt_num_fields] = SP_END;
832 string Scoreboard_AddPlayerId(string pl_name, entity pl)
834 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
835 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
836 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
840 vector sbt_field_rgb;
841 string sbt_field_icon0;
842 string sbt_field_icon1;
843 string sbt_field_icon2;
844 vector sbt_field_icon0_rgb;
845 vector sbt_field_icon1_rgb;
846 vector sbt_field_icon2_rgb;
847 string Scoreboard_GetName(entity pl)
849 if(ready_waiting && pl.ready)
851 sbt_field_icon0 = "gfx/scoreboard/player_ready";
855 int f = entcs_GetClientColors(pl.sv_entnum);
857 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
858 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
859 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
860 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
861 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
864 return entcs_GetName(pl.sv_entnum);
867 string Scoreboard_GetField(entity pl, PlayerScoreField field)
869 float tmp, num, denom;
872 sbt_field_rgb = '1 1 1';
873 sbt_field_icon0 = "";
874 sbt_field_icon1 = "";
875 sbt_field_icon2 = "";
876 sbt_field_icon0_rgb = '1 1 1';
877 sbt_field_icon1_rgb = '1 1 1';
878 sbt_field_icon2_rgb = '1 1 1';
883 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
884 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
888 tmp = max(0, min(220, f-80)) / 220;
889 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
895 f = pl.ping_packetloss;
896 tmp = pl.ping_movementloss;
897 if(f == 0 && tmp == 0)
899 str = ftos(ceil(f * 100));
901 str = strcat(str, "~", ftos(ceil(tmp * 100)));
902 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
903 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
907 str = Scoreboard_GetName(pl);
908 if (autocvar_hud_panel_scoreboard_playerid)
909 str = Scoreboard_AddPlayerId(str, pl);
913 f = pl.(scores(SP_KILLS));
914 f -= pl.(scores(SP_SUICIDES));
918 num = pl.(scores(SP_KILLS));
919 denom = pl.(scores(SP_DEATHS));
922 sbt_field_rgb = '0 1 0';
923 str = sprintf("%d", num);
924 } else if(num <= 0) {
925 sbt_field_rgb = '1 0 0';
926 str = sprintf("%.1f", num/denom);
928 str = sprintf("%.1f", num/denom);
932 f = pl.(scores(SP_KILLS));
933 f -= pl.(scores(SP_DEATHS));
936 sbt_field_rgb = '0 1 0';
938 sbt_field_rgb = '1 1 1';
940 sbt_field_rgb = '1 0 0';
946 float elo = pl.(scores(SP_ELO));
948 case -1: return "...";
949 case -2: return _("N/A");
950 default: return ftos(elo);
956 float fps = pl.(scores(SP_FPS));
959 sbt_field_rgb = '1 1 1';
960 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
962 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
963 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
967 case SP_DMG: case SP_DMGTAKEN:
968 return sprintf("%.1f k", pl.(scores(field)) / 1000);
970 default: case SP_SCORE:
971 tmp = pl.(scores(field));
972 f = scores_flags(field);
973 if(field == ps_primary)
974 sbt_field_rgb = '1 1 0';
975 else if(field == ps_secondary)
976 sbt_field_rgb = '0 1 1';
978 sbt_field_rgb = '1 1 1';
979 return ScoreString(f, tmp);
984 float sbt_fixcolumnwidth_len;
985 float sbt_fixcolumnwidth_iconlen;
986 float sbt_fixcolumnwidth_marginlen;
988 string Scoreboard_FixColumnWidth(int i, string str)
994 sbt_fixcolumnwidth_iconlen = 0;
996 if(sbt_field_icon0 != "")
998 sz = draw_getimagesize(sbt_field_icon0);
1000 if(sbt_fixcolumnwidth_iconlen < f)
1001 sbt_fixcolumnwidth_iconlen = f;
1004 if(sbt_field_icon1 != "")
1006 sz = draw_getimagesize(sbt_field_icon1);
1008 if(sbt_fixcolumnwidth_iconlen < f)
1009 sbt_fixcolumnwidth_iconlen = f;
1012 if(sbt_field_icon2 != "")
1014 sz = draw_getimagesize(sbt_field_icon2);
1016 if(sbt_fixcolumnwidth_iconlen < f)
1017 sbt_fixcolumnwidth_iconlen = f;
1020 if(sbt_fixcolumnwidth_iconlen != 0)
1022 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1023 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1026 sbt_fixcolumnwidth_marginlen = 0;
1028 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1031 float remaining_space = 0;
1032 for(j = 0; j < sbt_num_fields; ++j)
1034 if (sbt_field[i] != SP_SEPARATOR)
1035 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1036 sbt_field_size[i] = panel_size.x - remaining_space;
1038 if (sbt_fixcolumnwidth_iconlen != 0)
1039 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1040 float namesize = panel_size.x - remaining_space;
1041 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1042 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1044 max_namesize = vid_conwidth - remaining_space;
1047 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1049 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1050 if(sbt_field_size[i] < f)
1051 sbt_field_size[i] = f;
1056 void Scoreboard_initFieldSizes()
1058 for(int i = 0; i < sbt_num_fields; ++i)
1060 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1061 Scoreboard_FixColumnWidth(i, "");
1065 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1068 vector column_dim = eY * panel_size.y;
1070 column_dim.y -= 1.25 * hud_fontsize.y;
1071 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1072 pos.x += hud_fontsize.x * 0.5;
1073 for(i = 0; i < sbt_num_fields; ++i)
1075 if(sbt_field[i] == SP_SEPARATOR)
1077 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1080 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1081 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1082 pos.x += column_dim.x;
1084 if(sbt_field[i] == SP_SEPARATOR)
1086 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1087 for(i = sbt_num_fields - 1; i > 0; --i)
1089 if(sbt_field[i] == SP_SEPARATOR)
1092 pos.x -= sbt_field_size[i];
1097 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1098 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1101 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1102 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1103 pos.x -= hud_fontsize.x;
1107 pos.x = panel_pos.x;
1108 pos.y += 1.25 * hud_fontsize.y;
1112 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1114 TC(bool, is_self); TC(int, pl_number);
1116 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1118 vector h_pos = item_pos;
1119 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1120 // alternated rows highlighting
1121 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1123 if (pl == scoreboard_selected_player)
1124 drawfill(h_pos, h_size, rgb, 0.44, DRAWFLAG_NORMAL);
1127 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1128 else if((sbt_highlight) && (!(pl_number % 2)))
1129 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1131 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1133 vector pos = item_pos;
1134 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1136 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1138 pos.x += hud_fontsize.x * 0.5;
1139 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1140 vector tmp = '0 0 0';
1142 PlayerScoreField field;
1143 for(i = 0; i < sbt_num_fields; ++i)
1145 field = sbt_field[i];
1146 if(field == SP_SEPARATOR)
1149 if(is_spec && field != SP_NAME && field != SP_PING) {
1150 pos.x += sbt_field_size[i] + hud_fontsize.x;
1153 str = Scoreboard_GetField(pl, field);
1154 str = Scoreboard_FixColumnWidth(i, str);
1156 pos.x += sbt_field_size[i] + hud_fontsize.x;
1158 if(field == SP_NAME) {
1159 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1160 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1162 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1163 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1166 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1167 if(sbt_field_icon0 != "")
1168 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1169 if(sbt_field_icon1 != "")
1170 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1171 if(sbt_field_icon2 != "")
1172 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1175 if(sbt_field[i] == SP_SEPARATOR)
1177 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1178 for(i = sbt_num_fields-1; i > 0; --i)
1180 field = sbt_field[i];
1181 if(field == SP_SEPARATOR)
1184 if(is_spec && field != SP_NAME && field != SP_PING) {
1185 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1189 str = Scoreboard_GetField(pl, field);
1190 str = Scoreboard_FixColumnWidth(i, str);
1192 if(field == SP_NAME) {
1193 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1194 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1196 tmp.x = sbt_fixcolumnwidth_len;
1197 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1200 tmp.x = sbt_field_size[i];
1201 if(sbt_field_icon0 != "")
1202 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1203 if(sbt_field_icon1 != "")
1204 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1205 if(sbt_field_icon2 != "")
1206 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1207 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1212 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1215 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1218 vector h_pos = item_pos;
1219 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1221 bool complete = (this_team == NUM_SPECTATOR);
1224 if((sbt_highlight) && (!(pl_number % 2)))
1225 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1227 vector pos = item_pos;
1228 pos.x += hud_fontsize.x * 0.5;
1229 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1231 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1233 width_limit -= stringwidth("...", false, hud_fontsize);
1234 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1235 static float max_name_width = 0;
1237 float fieldsize = 0;
1238 float min_fieldsize = 0;
1239 float fieldpadding = hud_fontsize.x * 0.25;
1240 if(this_team == NUM_SPECTATOR)
1242 if(autocvar_hud_panel_scoreboard_spectators_showping)
1243 min_fieldsize = stringwidth("999", false, hud_fontsize);
1245 else if(autocvar_hud_panel_scoreboard_others_showscore)
1246 min_fieldsize = stringwidth("99", false, hud_fontsize);
1247 for(i = 0; pl; pl = pl.sort_next)
1249 if(pl.team != this_team)
1251 if(pl == ignored_pl)
1255 if(this_team == NUM_SPECTATOR)
1257 if(autocvar_hud_panel_scoreboard_spectators_showping)
1258 field = Scoreboard_GetField(pl, SP_PING);
1260 else if(autocvar_hud_panel_scoreboard_others_showscore)
1261 field = Scoreboard_GetField(pl, SP_SCORE);
1263 string str = entcs_GetName(pl.sv_entnum);
1264 if (autocvar_hud_panel_scoreboard_playerid)
1265 str = Scoreboard_AddPlayerId(str, pl);
1266 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1267 float column_width = stringwidth(str, true, hud_fontsize);
1268 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1270 if(column_width > max_name_width)
1271 max_name_width = column_width;
1272 column_width = max_name_width;
1276 fieldsize = stringwidth(field, false, hud_fontsize);
1277 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1280 if(pos.x + column_width > width_limit)
1285 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1290 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1291 pos.y += hud_fontsize.y * 1.25;
1295 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1297 if (pl == scoreboard_selected_player)
1299 h_size.x = column_width + hud_fontsize.x * 0.25;
1300 h_size.y = hud_fontsize.y;
1301 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44, DRAWFLAG_NORMAL);
1305 vector name_pos = pos;
1306 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1307 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1308 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1311 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1312 h_size.y = hud_fontsize.y;
1313 vector field_pos = pos;
1314 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1315 field_pos.x += column_width - h_size.x;
1317 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1318 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1319 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1323 h_size.x = column_width + hud_fontsize.x * 0.25;
1324 h_size.y = hud_fontsize.y;
1325 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1327 pos.x += column_width;
1328 pos.x += hud_fontsize.x;
1330 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1333 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1335 int max_players = 999;
1336 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1338 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1341 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1342 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1343 height /= team_count;
1346 height -= panel_bg_padding * 2; // - padding
1347 max_players = floor(height / (hud_fontsize.y * 1.25));
1348 if(max_players <= 1)
1350 if(max_players == tm.team_size)
1355 entity me = playerslots[current_player];
1357 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1358 panel_size.y += panel_bg_padding * 2;
1360 vector scoreboard_selected_hl_pos = pos;
1361 vector scoreboard_selected_hl_size = '0 0 0';
1362 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1363 scoreboard_selected_hl_size.y = panel_size.y;
1367 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1368 if(panel.current_panel_bg != "0")
1369 end_pos.y += panel_bg_border * 2;
1371 if(panel_bg_padding)
1373 panel_pos += '1 1 0' * panel_bg_padding;
1374 panel_size -= '2 2 0' * panel_bg_padding;
1378 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1382 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1384 pos.y += 1.25 * hud_fontsize.y;
1387 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1389 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1392 // print header row and highlight columns
1393 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1395 // fill the table and draw the rows
1396 bool is_self = false;
1397 bool self_shown = false;
1399 for(pl = players.sort_next; pl; pl = pl.sort_next)
1401 if(pl.team != tm.team)
1403 if(i == max_players - 2 && pl != me)
1405 if(!self_shown && me.team == tm.team)
1407 Scoreboard_DrawItem(pos, rgb, me, true, i);
1409 pos.y += 1.25 * hud_fontsize.y;
1413 if(i >= max_players - 1)
1415 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1418 is_self = (pl.sv_entnum == current_player);
1419 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1422 pos.y += 1.25 * hud_fontsize.y;
1426 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1428 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1429 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.3, DRAWFLAG_NORMAL);
1432 panel_size.x += panel_bg_padding * 2; // restore initial width
1436 bool Scoreboard_WouldDraw()
1438 if (scoreboard_ui_enabled)
1440 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1442 else if (QuickMenu_IsOpened())
1444 else if (HUD_Radar_Clickable())
1446 else if (scoreboard_showscores)
1448 else if (intermission == 1)
1450 else if (intermission == 2)
1452 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1453 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1457 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1462 float average_accuracy;
1463 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1465 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1467 WepSet weapons_stat = WepSet_GetFromStat();
1468 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1469 int disownedcnt = 0;
1471 FOREACH(Weapons, it != WEP_Null, {
1472 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1474 WepSet set = it.m_wepset;
1475 if(it.spawnflags & WEP_TYPE_OTHER)
1480 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1482 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1489 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1490 if (weapon_cnt <= 0) return pos;
1493 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1495 int columns = ceil(weapon_cnt / rows);
1497 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1498 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1499 float height = weapon_height + hud_fontsize.y;
1501 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);
1502 pos.y += 1.25 * hud_fontsize.y;
1503 if(panel.current_panel_bg != "0")
1504 pos.y += panel_bg_border;
1507 panel_size.y = height * rows;
1508 panel_size.y += panel_bg_padding * 2;
1510 float panel_bg_alpha_save = panel_bg_alpha;
1511 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1513 panel_bg_alpha = panel_bg_alpha_save;
1515 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1516 if(panel.current_panel_bg != "0")
1517 end_pos.y += panel_bg_border * 2;
1519 if(panel_bg_padding)
1521 panel_pos += '1 1 0' * panel_bg_padding;
1522 panel_size -= '2 2 0' * panel_bg_padding;
1526 vector tmp = panel_size;
1528 float weapon_width = tmp.x / columns / rows;
1531 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1535 // column highlighting
1536 for (int i = 0; i < columns; ++i)
1538 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);
1541 for (int i = 0; i < rows; ++i)
1542 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1545 average_accuracy = 0;
1546 int weapons_with_stats = 0;
1548 pos.x += weapon_width / 2;
1550 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1553 Accuracy_LoadColors();
1555 float oldposx = pos.x;
1559 FOREACH(Weapons, it != WEP_Null, {
1560 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1562 WepSet set = it.m_wepset;
1563 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1565 if (it.spawnflags & WEP_TYPE_OTHER)
1569 if (weapon_stats >= 0)
1570 weapon_alpha = sbt_fg_alpha;
1572 weapon_alpha = 0.2 * sbt_fg_alpha;
1575 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1577 if (weapon_stats >= 0) {
1578 weapons_with_stats += 1;
1579 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1581 string s = sprintf("%d%%", weapon_stats * 100);
1582 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1584 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1585 rgb = Accuracy_GetColor(weapon_stats);
1587 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1589 tmpos.x += weapon_width * rows;
1590 pos.x += weapon_width * rows;
1591 if (rows == 2 && column == columns - 1) {
1599 if (weapons_with_stats)
1600 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1602 panel_size.x += panel_bg_padding * 2; // restore initial width
1607 bool is_item_filtered(entity it)
1609 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1611 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1614 if (it.instanceOfArmor || it.instanceOfHealth)
1616 int ha_mask = floor(mask) % 10;
1619 default: return false;
1620 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1621 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1622 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1623 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1626 if (it.instanceOfAmmo)
1628 int ammo_mask = floor(mask / 10) % 10;
1629 return (ammo_mask == 1);
1634 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1636 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1638 int disowned_cnt = 0;
1639 int uninteresting_cnt = 0;
1640 IL_EACH(default_order_items, true, {
1641 int q = g_inventory.inv_items[it.m_id];
1642 //q = 1; // debug: display all items
1643 if (is_item_filtered(it))
1644 ++uninteresting_cnt;
1648 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1649 int n = items_cnt - disowned_cnt;
1650 if (n <= 0) return pos;
1652 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1653 int columns = max(6, ceil(n / rows));
1655 float item_height = hud_fontsize.y * 2.3;
1656 float height = item_height + hud_fontsize.y;
1658 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1659 pos.y += 1.25 * hud_fontsize.y;
1660 if(panel.current_panel_bg != "0")
1661 pos.y += panel_bg_border;
1664 panel_size.y = height * rows;
1665 panel_size.y += panel_bg_padding * 2;
1667 float panel_bg_alpha_save = panel_bg_alpha;
1668 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1670 panel_bg_alpha = panel_bg_alpha_save;
1672 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1673 if(panel.current_panel_bg != "0")
1674 end_pos.y += panel_bg_border * 2;
1676 if(panel_bg_padding)
1678 panel_pos += '1 1 0' * panel_bg_padding;
1679 panel_size -= '2 2 0' * panel_bg_padding;
1683 vector tmp = panel_size;
1685 float item_width = tmp.x / columns / rows;
1688 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1692 // column highlighting
1693 for (int i = 0; i < columns; ++i)
1695 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);
1698 for (int i = 0; i < rows; ++i)
1699 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1703 pos.x += item_width / 2;
1705 float oldposx = pos.x;
1709 IL_EACH(default_order_items, !is_item_filtered(it), {
1710 int n = g_inventory.inv_items[it.m_id];
1711 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1712 if (n <= 0) continue;
1713 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);
1715 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1716 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1717 tmpos.x += item_width * rows;
1718 pos.x += item_width * rows;
1719 if (rows == 2 && column == columns - 1) {
1727 panel_size.x += panel_bg_padding * 2; // restore initial width
1732 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1734 pos.x += hud_fontsize.x * 0.25;
1735 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1736 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1737 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1739 pos.y += hud_fontsize.y;
1744 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1745 float stat_secrets_found, stat_secrets_total;
1746 float stat_monsters_killed, stat_monsters_total;
1750 // get monster stats
1751 stat_monsters_killed = STAT(MONSTERS_KILLED);
1752 stat_monsters_total = STAT(MONSTERS_TOTAL);
1754 // get secrets stats
1755 stat_secrets_found = STAT(SECRETS_FOUND);
1756 stat_secrets_total = STAT(SECRETS_TOTAL);
1758 // get number of rows
1759 if(stat_secrets_total)
1761 if(stat_monsters_total)
1764 // if no rows, return
1768 // draw table header
1769 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1770 pos.y += 1.25 * hud_fontsize.y;
1771 if(panel.current_panel_bg != "0")
1772 pos.y += panel_bg_border;
1775 panel_size.y = hud_fontsize.y * rows;
1776 panel_size.y += panel_bg_padding * 2;
1779 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1780 if(panel.current_panel_bg != "0")
1781 end_pos.y += panel_bg_border * 2;
1783 if(panel_bg_padding)
1785 panel_pos += '1 1 0' * panel_bg_padding;
1786 panel_size -= '2 2 0' * panel_bg_padding;
1790 vector tmp = panel_size;
1793 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1796 if(stat_monsters_total)
1798 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1799 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1803 if(stat_secrets_total)
1805 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1806 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1809 panel_size.x += panel_bg_padding * 2; // restore initial width
1813 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1816 RANKINGS_RECEIVED_CNT = 0;
1817 for (i=RANKINGS_CNT-1; i>=0; --i)
1819 ++RANKINGS_RECEIVED_CNT;
1821 if (RANKINGS_RECEIVED_CNT == 0)
1824 vector hl_rgb = rgb + '0.5 0.5 0.5';
1826 vector scoreboard_selected_hl_pos = pos;
1828 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1829 pos.y += 1.25 * hud_fontsize.y;
1830 if(panel.current_panel_bg != "0")
1831 pos.y += panel_bg_border;
1833 vector scoreboard_selected_hl_size = '0 0 0';
1834 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1835 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1840 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1842 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1847 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1849 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1853 float ranksize = 3 * hud_fontsize.x;
1854 float timesize = 5 * hud_fontsize.x;
1855 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1856 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1857 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1860 rankings_cnt = RANKINGS_RECEIVED_CNT;
1861 rankings_rows = ceil(rankings_cnt / rankings_columns);
1864 // expand name column to fill the entire row
1865 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1866 namesize += available_space;
1867 columnsize.x += available_space;
1869 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1870 panel_size.y += panel_bg_padding * 2;
1871 scoreboard_selected_hl_size.y += panel_size.y;
1875 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1876 if(panel.current_panel_bg != "0")
1877 end_pos.y += panel_bg_border * 2;
1879 if(panel_bg_padding)
1881 panel_pos += '1 1 0' * panel_bg_padding;
1882 panel_size -= '2 2 0' * panel_bg_padding;
1888 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1890 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1892 int column = 0, j = 0;
1893 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1894 int start_item = rankings_start_column * rankings_rows;
1895 for(i = start_item; i < start_item + rankings_cnt; ++i)
1897 int t = grecordtime[i];
1901 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1902 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1903 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
1904 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1906 str = count_ordinal(i+1);
1907 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1908 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1909 str = ColorTranslateRGB(grecordholder[i]);
1911 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1912 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1914 pos.y += 1.25 * hud_fontsize.y;
1916 if(j >= rankings_rows)
1920 pos.x += panel_size.x / rankings_columns;
1921 pos.y = panel_pos.y;
1924 strfree(zoned_name_self);
1926 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
1928 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1929 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
1932 panel_size.x += panel_bg_padding * 2; // restore initial width
1936 bool have_weapon_stats;
1937 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1939 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1941 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1944 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1945 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1951 if (!have_weapon_stats)
1953 FOREACH(Weapons, it != WEP_Null, {
1954 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1955 if (weapon_stats >= 0)
1957 have_weapon_stats = true;
1961 if (!have_weapon_stats)
1968 bool have_item_stats;
1969 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1971 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1973 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1976 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1977 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1983 if (!have_item_stats)
1985 IL_EACH(default_order_items, true, {
1986 if (!is_item_filtered(it))
1988 int q = g_inventory.inv_items[it.m_id];
1989 //q = 1; // debug: display all items
1992 have_item_stats = true;
1997 if (!have_item_stats)
2004 vector Scoreboard_Spectators_Draw(vector pos) {
2009 for(pl = players.sort_next; pl; pl = pl.sort_next)
2011 if(pl.team == NUM_SPECTATOR)
2013 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2014 if(tm.team == NUM_SPECTATOR)
2016 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2017 draw_beginBoldFont();
2018 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2020 pos.y += 1.25 * hud_fontsize.y;
2022 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2023 pos.y += 1.25 * hud_fontsize.y;
2028 if (str != "") // if there's at least one spectator
2029 pos.y += 0.5 * hud_fontsize.y;
2034 void Scoreboard_Draw()
2036 if(!autocvar__hud_configure)
2038 if(!hud_draw_maximized) return;
2040 // frametime checks allow to toggle the scoreboard even when the game is paused
2041 if(scoreboard_active) {
2042 if (scoreboard_fade_alpha == 0)
2043 scoreboard_time = time;
2044 if(hud_configure_menu_open == 1)
2045 scoreboard_fade_alpha = 1;
2046 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2047 if (scoreboard_fadeinspeed && frametime)
2048 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2050 scoreboard_fade_alpha = 1;
2051 if(hud_fontsize_str != autocvar_hud_fontsize)
2053 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2054 Scoreboard_initFieldSizes();
2055 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2059 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2060 if (scoreboard_fadeoutspeed && frametime)
2061 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2063 scoreboard_fade_alpha = 0;
2066 if (!scoreboard_fade_alpha)
2068 scoreboard_acc_fade_alpha = 0;
2069 scoreboard_itemstats_fade_alpha = 0;
2074 scoreboard_fade_alpha = 0;
2076 if (autocvar_hud_panel_scoreboard_dynamichud)
2079 HUD_Scale_Disable();
2081 if(scoreboard_fade_alpha <= 0)
2083 panel_fade_alpha *= scoreboard_fade_alpha;
2084 HUD_Panel_LoadCvars();
2086 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2087 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2088 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2089 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2090 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2091 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2092 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2094 // don't overlap with con_notify
2095 if(!autocvar__hud_configure)
2096 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2098 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2099 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2100 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2101 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2102 panel_pos.x = scoreboard_left;
2103 panel_size.x = fixed_scoreboard_width;
2105 Scoreboard_UpdatePlayerTeams();
2107 scoreboard_top = panel_pos.y;
2108 vector pos = panel_pos;
2113 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2115 // Begin of Game Info Section
2116 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2117 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2119 // Game Info: Game Type
2120 str = MapInfo_Type_ToText(gametype);
2121 draw_beginBoldFont();
2122 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);
2125 // Game Info: Game Detail
2126 float tl = STAT(TIMELIMIT);
2127 float fl = STAT(FRAGLIMIT);
2128 float ll = STAT(LEADLIMIT);
2129 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2132 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2133 if(!gametype.m_hidelimits)
2138 str = strcat(str, "^7 / "); // delimiter
2141 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2142 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2143 (teamscores_label(ts_primary) == "fastest") ? "" :
2144 TranslateScoresLabel(teamscores_label(ts_primary))));
2148 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2149 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2150 (scores_label(ps_primary) == "fastest") ? "" :
2151 TranslateScoresLabel(scores_label(ps_primary))));
2156 if(tl > 0 || fl > 0)
2159 if (ll_and_fl && fl > 0)
2160 str = strcat(str, "^7 & ");
2162 str = strcat(str, "^7 / ");
2167 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2168 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2169 (teamscores_label(ts_primary) == "fastest") ? "" :
2170 TranslateScoresLabel(teamscores_label(ts_primary))));
2174 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2175 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2176 (scores_label(ps_primary) == "fastest") ? "" :
2177 TranslateScoresLabel(scores_label(ps_primary))));
2182 pos.y += sb_gameinfo_type_fontsize.y;
2183 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
2185 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2186 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2187 // End of Game Info Section
2189 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2190 if(panel.current_panel_bg != "0")
2191 pos.y += panel_bg_border;
2193 // Draw the scoreboard
2194 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2197 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2201 vector panel_bg_color_save = panel_bg_color;
2202 vector team_score_baseoffset;
2203 vector team_size_baseoffset;
2204 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2206 // put team score to the left of scoreboard (and team size to the right)
2207 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2208 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2209 if(panel.current_panel_bg != "0")
2211 team_score_baseoffset.x -= panel_bg_border;
2212 team_size_baseoffset.x += panel_bg_border;
2217 // put team score to the right of scoreboard (and team size to the left)
2218 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2219 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2220 if(panel.current_panel_bg != "0")
2222 team_score_baseoffset.x += panel_bg_border;
2223 team_size_baseoffset.x -= panel_bg_border;
2227 int team_size_total = 0;
2228 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2230 // calculate team size total (sum of all team sizes)
2231 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2232 if(tm.team != NUM_SPECTATOR)
2233 team_size_total += tm.team_size;
2236 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2238 if(tm.team == NUM_SPECTATOR)
2243 draw_beginBoldFont();
2244 vector rgb = Team_ColorRGB(tm.team);
2245 str = ftos(tm.(teamscores(ts_primary)));
2246 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2248 // team score on the left (default)
2249 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2253 // team score on the right
2254 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2256 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2258 // team size (if set to show on the side)
2259 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2261 // calculate the starting position for the whole team size info string
2262 str = sprintf("%d/%d", tm.team_size, team_size_total);
2263 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2265 // team size on the left
2266 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2270 // team size on the right
2271 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2273 str = sprintf("%d", tm.team_size);
2274 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2275 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2276 str = sprintf("/%d", team_size_total);
2277 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2281 // secondary score, e.g. keyhunt
2282 if(ts_primary != ts_secondary)
2284 str = ftos(tm.(teamscores(ts_secondary)));
2285 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2288 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2293 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2296 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2299 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2300 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2301 else if(panel_bg_color_team > 0)
2302 panel_bg_color = rgb * panel_bg_color_team;
2304 panel_bg_color = rgb;
2305 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2307 panel_bg_color = panel_bg_color_save;
2311 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2312 if(tm.team != NUM_SPECTATOR)
2315 // display it anyway
2316 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2319 // draw scoreboard spectators before accuracy and item stats
2320 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2321 pos = Scoreboard_Spectators_Draw(pos);
2324 // draw accuracy and item stats
2325 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2326 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2327 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2328 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2330 // draw scoreboard spectators after accuracy and item stats and before rankings
2331 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2332 pos = Scoreboard_Spectators_Draw(pos);
2335 if(MUTATOR_CALLHOOK(ShowRankings)) {
2336 string ranktitle = M_ARGV(0, string);
2337 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2338 if(race_speedaward_alltimebest)
2341 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2345 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2346 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2347 str = strcat(str, " / ");
2349 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2350 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2351 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2352 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2354 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2359 // draw scoreboard spectators after rankings
2360 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2361 pos = Scoreboard_Spectators_Draw(pos);
2364 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2366 // draw scoreboard spectators after mapstats
2367 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2368 pos = Scoreboard_Spectators_Draw(pos);
2372 // print information about respawn status
2373 float respawn_time = STAT(RESPAWN_TIME);
2377 if(respawn_time < 0)
2379 // a negative number means we are awaiting respawn, time value is still the same
2380 respawn_time *= -1; // remove mark now that we checked it
2382 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2383 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2385 str = sprintf(_("^1Respawning in ^3%s^1..."),
2386 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2387 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2389 count_seconds(ceil(respawn_time - time))
2393 else if(time < respawn_time)
2395 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2396 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2397 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2399 count_seconds(ceil(respawn_time - time))
2403 else if(time >= respawn_time)
2404 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2406 pos.y += 1.2 * hud_fontsize.y;
2407 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2410 pos.y += hud_fontsize.y;
2411 if (scoreboard_fade_alpha < 1)
2412 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2413 else if (pos.y != scoreboard_bottom)
2415 if (pos.y > scoreboard_bottom)
2416 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2418 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2423 if (scoreboard_fade_alpha == 1)
2425 if (scoreboard_bottom > 0.95 * vid_conheight)
2426 rankings_rows = max(1, rankings_rows - 1);
2427 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2428 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2430 rankings_cnt = rankings_rows * rankings_columns;