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 (MUTATOR_CALLHOOK(DrawScoreboard))
1440 else if (QuickMenu_IsOpened())
1442 else if (HUD_Radar_Clickable())
1444 else if (scoreboard_showscores)
1446 else if (intermission == 1)
1448 else if (intermission == 2)
1450 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1451 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1455 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1460 float average_accuracy;
1461 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1463 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1465 WepSet weapons_stat = WepSet_GetFromStat();
1466 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1467 int disownedcnt = 0;
1469 FOREACH(Weapons, it != WEP_Null, {
1470 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1472 WepSet set = it.m_wepset;
1473 if(it.spawnflags & WEP_TYPE_OTHER)
1478 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1480 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1487 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1488 if (weapon_cnt <= 0) return pos;
1491 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1493 int columns = ceil(weapon_cnt / rows);
1495 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1496 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1497 float height = weapon_height + hud_fontsize.y;
1499 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);
1500 pos.y += 1.25 * hud_fontsize.y;
1501 if(panel.current_panel_bg != "0")
1502 pos.y += panel_bg_border;
1505 panel_size.y = height * rows;
1506 panel_size.y += panel_bg_padding * 2;
1508 float panel_bg_alpha_save = panel_bg_alpha;
1509 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1511 panel_bg_alpha = panel_bg_alpha_save;
1513 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1514 if(panel.current_panel_bg != "0")
1515 end_pos.y += panel_bg_border * 2;
1517 if(panel_bg_padding)
1519 panel_pos += '1 1 0' * panel_bg_padding;
1520 panel_size -= '2 2 0' * panel_bg_padding;
1524 vector tmp = panel_size;
1526 float weapon_width = tmp.x / columns / rows;
1529 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1533 // column highlighting
1534 for (int i = 0; i < columns; ++i)
1536 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);
1539 for (int i = 0; i < rows; ++i)
1540 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1543 average_accuracy = 0;
1544 int weapons_with_stats = 0;
1546 pos.x += weapon_width / 2;
1548 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1551 Accuracy_LoadColors();
1553 float oldposx = pos.x;
1557 FOREACH(Weapons, it != WEP_Null, {
1558 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1560 WepSet set = it.m_wepset;
1561 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1563 if (it.spawnflags & WEP_TYPE_OTHER)
1567 if (weapon_stats >= 0)
1568 weapon_alpha = sbt_fg_alpha;
1570 weapon_alpha = 0.2 * sbt_fg_alpha;
1573 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1575 if (weapon_stats >= 0) {
1576 weapons_with_stats += 1;
1577 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1579 string s = sprintf("%d%%", weapon_stats * 100);
1580 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1582 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1583 rgb = Accuracy_GetColor(weapon_stats);
1585 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1587 tmpos.x += weapon_width * rows;
1588 pos.x += weapon_width * rows;
1589 if (rows == 2 && column == columns - 1) {
1597 if (weapons_with_stats)
1598 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1600 panel_size.x += panel_bg_padding * 2; // restore initial width
1605 bool is_item_filtered(entity it)
1607 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1609 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1612 if (it.instanceOfArmor || it.instanceOfHealth)
1614 int ha_mask = floor(mask) % 10;
1617 default: return false;
1618 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1619 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1620 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1621 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1624 if (it.instanceOfAmmo)
1626 int ammo_mask = floor(mask / 10) % 10;
1627 return (ammo_mask == 1);
1632 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1634 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1636 int disowned_cnt = 0;
1637 int uninteresting_cnt = 0;
1638 IL_EACH(default_order_items, true, {
1639 int q = g_inventory.inv_items[it.m_id];
1640 //q = 1; // debug: display all items
1641 if (is_item_filtered(it))
1642 ++uninteresting_cnt;
1646 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1647 int n = items_cnt - disowned_cnt;
1648 if (n <= 0) return pos;
1650 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1651 int columns = max(6, ceil(n / rows));
1653 float item_height = hud_fontsize.y * 2.3;
1654 float height = item_height + hud_fontsize.y;
1656 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1657 pos.y += 1.25 * hud_fontsize.y;
1658 if(panel.current_panel_bg != "0")
1659 pos.y += panel_bg_border;
1662 panel_size.y = height * rows;
1663 panel_size.y += panel_bg_padding * 2;
1665 float panel_bg_alpha_save = panel_bg_alpha;
1666 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1668 panel_bg_alpha = panel_bg_alpha_save;
1670 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1671 if(panel.current_panel_bg != "0")
1672 end_pos.y += panel_bg_border * 2;
1674 if(panel_bg_padding)
1676 panel_pos += '1 1 0' * panel_bg_padding;
1677 panel_size -= '2 2 0' * panel_bg_padding;
1681 vector tmp = panel_size;
1683 float item_width = tmp.x / columns / rows;
1686 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1690 // column highlighting
1691 for (int i = 0; i < columns; ++i)
1693 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);
1696 for (int i = 0; i < rows; ++i)
1697 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1701 pos.x += item_width / 2;
1703 float oldposx = pos.x;
1707 IL_EACH(default_order_items, !is_item_filtered(it), {
1708 int n = g_inventory.inv_items[it.m_id];
1709 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1710 if (n <= 0) continue;
1711 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);
1713 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1714 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1715 tmpos.x += item_width * rows;
1716 pos.x += item_width * rows;
1717 if (rows == 2 && column == columns - 1) {
1725 panel_size.x += panel_bg_padding * 2; // restore initial width
1730 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1732 pos.x += hud_fontsize.x * 0.25;
1733 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1734 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1735 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1737 pos.y += hud_fontsize.y;
1742 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1743 float stat_secrets_found, stat_secrets_total;
1744 float stat_monsters_killed, stat_monsters_total;
1748 // get monster stats
1749 stat_monsters_killed = STAT(MONSTERS_KILLED);
1750 stat_monsters_total = STAT(MONSTERS_TOTAL);
1752 // get secrets stats
1753 stat_secrets_found = STAT(SECRETS_FOUND);
1754 stat_secrets_total = STAT(SECRETS_TOTAL);
1756 // get number of rows
1757 if(stat_secrets_total)
1759 if(stat_monsters_total)
1762 // if no rows, return
1766 // draw table header
1767 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1768 pos.y += 1.25 * hud_fontsize.y;
1769 if(panel.current_panel_bg != "0")
1770 pos.y += panel_bg_border;
1773 panel_size.y = hud_fontsize.y * rows;
1774 panel_size.y += panel_bg_padding * 2;
1777 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1778 if(panel.current_panel_bg != "0")
1779 end_pos.y += panel_bg_border * 2;
1781 if(panel_bg_padding)
1783 panel_pos += '1 1 0' * panel_bg_padding;
1784 panel_size -= '2 2 0' * panel_bg_padding;
1788 vector tmp = panel_size;
1791 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1794 if(stat_monsters_total)
1796 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1797 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1801 if(stat_secrets_total)
1803 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1804 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1807 panel_size.x += panel_bg_padding * 2; // restore initial width
1811 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1814 RANKINGS_RECEIVED_CNT = 0;
1815 for (i=RANKINGS_CNT-1; i>=0; --i)
1817 ++RANKINGS_RECEIVED_CNT;
1819 if (RANKINGS_RECEIVED_CNT == 0)
1822 vector hl_rgb = rgb + '0.5 0.5 0.5';
1824 vector scoreboard_selected_hl_pos = pos;
1826 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1827 pos.y += 1.25 * hud_fontsize.y;
1828 if(panel.current_panel_bg != "0")
1829 pos.y += panel_bg_border;
1831 vector scoreboard_selected_hl_size = '0 0 0';
1832 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1833 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1838 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1840 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1845 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1847 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1851 float ranksize = 3 * hud_fontsize.x;
1852 float timesize = 5 * hud_fontsize.x;
1853 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1854 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1855 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1858 rankings_cnt = RANKINGS_RECEIVED_CNT;
1859 rankings_rows = ceil(rankings_cnt / rankings_columns);
1862 // expand name column to fill the entire row
1863 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1864 namesize += available_space;
1865 columnsize.x += available_space;
1867 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1868 panel_size.y += panel_bg_padding * 2;
1869 scoreboard_selected_hl_size.y += panel_size.y;
1873 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1874 if(panel.current_panel_bg != "0")
1875 end_pos.y += panel_bg_border * 2;
1877 if(panel_bg_padding)
1879 panel_pos += '1 1 0' * panel_bg_padding;
1880 panel_size -= '2 2 0' * panel_bg_padding;
1886 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1888 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1890 int column = 0, j = 0;
1891 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1892 int start_item = rankings_start_column * rankings_rows;
1893 for(i = start_item; i < start_item + rankings_cnt; ++i)
1895 int t = grecordtime[i];
1899 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1900 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1901 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
1902 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1904 str = count_ordinal(i+1);
1905 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1906 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1907 str = ColorTranslateRGB(grecordholder[i]);
1909 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1910 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1912 pos.y += 1.25 * hud_fontsize.y;
1914 if(j >= rankings_rows)
1918 pos.x += panel_size.x / rankings_columns;
1919 pos.y = panel_pos.y;
1922 strfree(zoned_name_self);
1924 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
1926 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1927 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
1930 panel_size.x += panel_bg_padding * 2; // restore initial width
1934 bool have_weapon_stats;
1935 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1937 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1939 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1942 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1943 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1949 if (!have_weapon_stats)
1951 FOREACH(Weapons, it != WEP_Null, {
1952 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1953 if (weapon_stats >= 0)
1955 have_weapon_stats = true;
1959 if (!have_weapon_stats)
1966 bool have_item_stats;
1967 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1969 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1971 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1974 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1975 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1981 if (!have_item_stats)
1983 IL_EACH(default_order_items, true, {
1984 if (!is_item_filtered(it))
1986 int q = g_inventory.inv_items[it.m_id];
1987 //q = 1; // debug: display all items
1990 have_item_stats = true;
1995 if (!have_item_stats)
2002 vector Scoreboard_Spectators_Draw(vector pos) {
2007 for(pl = players.sort_next; pl; pl = pl.sort_next)
2009 if(pl.team == NUM_SPECTATOR)
2011 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2012 if(tm.team == NUM_SPECTATOR)
2014 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2015 draw_beginBoldFont();
2016 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2018 pos.y += 1.25 * hud_fontsize.y;
2020 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2021 pos.y += 1.25 * hud_fontsize.y;
2026 if (str != "") // if there's at least one spectator
2027 pos.y += 0.5 * hud_fontsize.y;
2032 void Scoreboard_Draw()
2034 if(!autocvar__hud_configure)
2036 if(!hud_draw_maximized) return;
2038 // frametime checks allow to toggle the scoreboard even when the game is paused
2039 if(scoreboard_active) {
2040 if (scoreboard_fade_alpha == 0)
2041 scoreboard_time = time;
2042 if(hud_configure_menu_open == 1)
2043 scoreboard_fade_alpha = 1;
2044 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2045 if (scoreboard_fadeinspeed && frametime)
2046 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2048 scoreboard_fade_alpha = 1;
2049 if(hud_fontsize_str != autocvar_hud_fontsize)
2051 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2052 Scoreboard_initFieldSizes();
2053 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2057 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2058 if (scoreboard_fadeoutspeed && frametime)
2059 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2061 scoreboard_fade_alpha = 0;
2064 if (!scoreboard_fade_alpha)
2066 scoreboard_acc_fade_alpha = 0;
2067 scoreboard_itemstats_fade_alpha = 0;
2072 scoreboard_fade_alpha = 0;
2074 if (autocvar_hud_panel_scoreboard_dynamichud)
2077 HUD_Scale_Disable();
2079 if(scoreboard_fade_alpha <= 0)
2081 panel_fade_alpha *= scoreboard_fade_alpha;
2082 HUD_Panel_LoadCvars();
2084 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2085 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2086 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2087 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2088 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2089 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2090 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2092 // don't overlap with con_notify
2093 if(!autocvar__hud_configure)
2094 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2096 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2097 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2098 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2099 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2100 panel_pos.x = scoreboard_left;
2101 panel_size.x = fixed_scoreboard_width;
2103 Scoreboard_UpdatePlayerTeams();
2105 scoreboard_top = panel_pos.y;
2106 vector pos = panel_pos;
2111 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2113 // Begin of Game Info Section
2114 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2115 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2117 // Game Info: Game Type
2118 str = MapInfo_Type_ToText(gametype);
2119 draw_beginBoldFont();
2120 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);
2123 // Game Info: Game Detail
2124 float tl = STAT(TIMELIMIT);
2125 float fl = STAT(FRAGLIMIT);
2126 float ll = STAT(LEADLIMIT);
2127 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2130 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2131 if(!gametype.m_hidelimits)
2136 str = strcat(str, "^7 / "); // delimiter
2139 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2140 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2141 (teamscores_label(ts_primary) == "fastest") ? "" :
2142 TranslateScoresLabel(teamscores_label(ts_primary))));
2146 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2147 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2148 (scores_label(ps_primary) == "fastest") ? "" :
2149 TranslateScoresLabel(scores_label(ps_primary))));
2154 if(tl > 0 || fl > 0)
2157 if (ll_and_fl && fl > 0)
2158 str = strcat(str, "^7 & ");
2160 str = strcat(str, "^7 / ");
2165 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2166 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2167 (teamscores_label(ts_primary) == "fastest") ? "" :
2168 TranslateScoresLabel(teamscores_label(ts_primary))));
2172 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2173 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2174 (scores_label(ps_primary) == "fastest") ? "" :
2175 TranslateScoresLabel(scores_label(ps_primary))));
2180 pos.y += sb_gameinfo_type_fontsize.y;
2181 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
2183 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2184 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2185 // End of Game Info Section
2187 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2188 if(panel.current_panel_bg != "0")
2189 pos.y += panel_bg_border;
2191 // Draw the scoreboard
2192 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2195 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2199 vector panel_bg_color_save = panel_bg_color;
2200 vector team_score_baseoffset;
2201 vector team_size_baseoffset;
2202 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2204 // put team score to the left of scoreboard (and team size to the right)
2205 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2206 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2207 if(panel.current_panel_bg != "0")
2209 team_score_baseoffset.x -= panel_bg_border;
2210 team_size_baseoffset.x += panel_bg_border;
2215 // put team score to the right of scoreboard (and team size to the left)
2216 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2217 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2218 if(panel.current_panel_bg != "0")
2220 team_score_baseoffset.x += panel_bg_border;
2221 team_size_baseoffset.x -= panel_bg_border;
2225 int team_size_total = 0;
2226 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2228 // calculate team size total (sum of all team sizes)
2229 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2230 if(tm.team != NUM_SPECTATOR)
2231 team_size_total += tm.team_size;
2234 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2236 if(tm.team == NUM_SPECTATOR)
2241 draw_beginBoldFont();
2242 vector rgb = Team_ColorRGB(tm.team);
2243 str = ftos(tm.(teamscores(ts_primary)));
2244 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2246 // team score on the left (default)
2247 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2251 // team score on the right
2252 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2254 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2256 // team size (if set to show on the side)
2257 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2259 // calculate the starting position for the whole team size info string
2260 str = sprintf("%d/%d", tm.team_size, team_size_total);
2261 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2263 // team size on the left
2264 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2268 // team size on the right
2269 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2271 str = sprintf("%d", tm.team_size);
2272 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2273 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2274 str = sprintf("/%d", team_size_total);
2275 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2279 // secondary score, e.g. keyhunt
2280 if(ts_primary != ts_secondary)
2282 str = ftos(tm.(teamscores(ts_secondary)));
2283 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2286 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2291 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2294 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2297 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2298 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2299 else if(panel_bg_color_team > 0)
2300 panel_bg_color = rgb * panel_bg_color_team;
2302 panel_bg_color = rgb;
2303 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2305 panel_bg_color = panel_bg_color_save;
2309 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2310 if(tm.team != NUM_SPECTATOR)
2313 // display it anyway
2314 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2317 // draw scoreboard spectators before accuracy and item stats
2318 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2319 pos = Scoreboard_Spectators_Draw(pos);
2322 // draw accuracy and item stats
2323 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2324 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2325 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2326 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2328 // draw scoreboard spectators after accuracy and item stats and before rankings
2329 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2330 pos = Scoreboard_Spectators_Draw(pos);
2333 if(MUTATOR_CALLHOOK(ShowRankings)) {
2334 string ranktitle = M_ARGV(0, string);
2335 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2336 if(race_speedaward_alltimebest)
2339 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2343 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2344 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2345 str = strcat(str, " / ");
2347 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2348 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2349 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2350 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2352 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2357 // draw scoreboard spectators after rankings
2358 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2359 pos = Scoreboard_Spectators_Draw(pos);
2362 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2364 // draw scoreboard spectators after mapstats
2365 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2366 pos = Scoreboard_Spectators_Draw(pos);
2370 // print information about respawn status
2371 float respawn_time = STAT(RESPAWN_TIME);
2375 if(respawn_time < 0)
2377 // a negative number means we are awaiting respawn, time value is still the same
2378 respawn_time *= -1; // remove mark now that we checked it
2380 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2381 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2383 str = sprintf(_("^1Respawning in ^3%s^1..."),
2384 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2385 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2387 count_seconds(ceil(respawn_time - time))
2391 else if(time < respawn_time)
2393 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2394 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2395 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2397 count_seconds(ceil(respawn_time - time))
2401 else if(time >= respawn_time)
2402 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2404 pos.y += 1.2 * hud_fontsize.y;
2405 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2408 pos.y += hud_fontsize.y;
2409 if (scoreboard_fade_alpha < 1)
2410 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2411 else if (pos.y != scoreboard_bottom)
2413 if (pos.y > scoreboard_bottom)
2414 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2416 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2421 if (scoreboard_fade_alpha == 1)
2423 if (scoreboard_bottom > 0.95 * vid_conheight)
2424 rankings_rows = max(1, rankings_rows - 1);
2425 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2426 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2428 rankings_cnt = rankings_rows * rankings_columns;