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_team_size_position = 0;
84 float autocvar_hud_panel_scoreboard_spectators_position = 1;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_itemstats = true;
93 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
94 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
95 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
96 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
99 bool autocvar_hud_panel_scoreboard_dynamichud = false;
101 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
102 bool autocvar_hud_panel_scoreboard_others_showscore = true;
103 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
104 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
105 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
106 bool autocvar_hud_panel_scoreboard_playerid = false;
107 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
108 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
109 bool autocvar_hud_panel_scoreboard_scores_per_round;
111 float scoreboard_time;
115 if(autocvar_hud_panel_scoreboard_scores_per_round)
116 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
119 // TODO: remove these after merging pending-release which contains Survival code
120 #define SURV_STATUS_HUNTER_TEMPSTRING _("Survivor")
121 #define SURV_STATUS_PREY_TEMPSTRING _("Hunter")
123 // mode 0: returns translated label
124 // mode 1: prints name and description of all the labels
125 string Label_getInfo(string label, int mode)
128 label = "bckills"; // first case in the switch
132 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
133 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
134 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")));
135 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
136 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
137 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
138 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
139 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
140 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
141 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
142 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
143 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
144 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
145 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
146 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
147 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
148 case "hunts": if (!mode) return CTX(_("SCO^hunts")); else LOG_HELP(strcat("^3", "hunts", " ^7", _("Number of hunts (Survival)")));
149 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
150 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
151 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
152 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
153 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
154 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
155 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
156 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
157 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
158 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
159 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
160 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")));
161 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
162 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
163 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
164 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
165 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
166 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
167 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
168 case "rounds_pl": if (!mode) return CTX(_("SCO^rounds played"));else LOG_HELP(strcat("^3", "rounds_pl", " ^7", _("Number of rounds played")));
169 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
170 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
171 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
172 case "survivals": if (!mode) return CTX(_("SCO^survivals")); else LOG_HELP(strcat("^3", "survivals", " ^7", _("Number of survivals")));
173 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
174 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
175 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
176 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
177 default: return label;
182 bool scoreboard_ui_disabling;
183 void HUD_Scoreboard_UI_Disable()
185 scoreboard_ui_disabling = true;
186 sb_showscores = false;
189 void HUD_Scoreboard_UI_Disable_Instantly()
191 scoreboard_ui_disabling = false;
192 scoreboard_ui_enabled = 0;
193 scoreboard_selected_panel = 0;
194 scoreboard_selected_player = NULL;
195 scoreboard_selected_team = NULL;
198 // mode: 0 normal, 1 team selection
199 void Scoreboard_UI_Enable(int mode)
205 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
208 // release player's pressed keys as they aren't released elsewhere
209 // in particular jump needs to be released as it may open the team selection
210 // (when server detects jump has been pressed it sends the command to open the team selection)
211 Release_Common_Keys();
212 scoreboard_ui_enabled = 2;
213 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
217 if (scoreboard_ui_enabled == 1)
219 scoreboard_ui_enabled = 1;
220 scoreboard_selected_panel = SB_PANEL_FIRST;
222 scoreboard_selected_player = NULL;
223 scoreboard_selected_team = NULL;
224 scoreboard_selected_panel_time = time;
227 int rankings_start_column;
228 int rankings_rows = 0;
229 int rankings_columns = 0;
230 int rankings_cnt = 0;
231 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
235 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
240 mousepos.x = nPrimary;
241 mousepos.y = nSecondary;
248 // at this point bInputType can only be 0 or 1 (key pressed or released)
249 bool key_pressed = (bInputType == 0);
251 // ESC to exit (TAB-ESC works too)
252 if(nPrimary == K_ESCAPE)
256 HUD_Scoreboard_UI_Disable();
260 // block any input while a menu dialog is fading
261 if(autocvar__menu_alpha)
267 // allow console bind to work
268 string con_keys = findkeysforcommand("toggleconsole", 0);
269 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
271 bool hit_con_bind = false;
273 for (i = 0; i < keys; ++i)
275 if(nPrimary == stof(argv(i)))
280 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
281 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
282 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
283 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
286 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
287 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
288 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
289 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
292 if(nPrimary == K_TAB)
296 if (scoreboard_ui_enabled == 2)
298 if (hudShiftState & S_SHIFT)
301 goto downarrow_action;
304 if (hudShiftState & S_SHIFT)
306 --scoreboard_selected_panel;
307 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
308 --scoreboard_selected_panel;
309 if (scoreboard_selected_panel < SB_PANEL_FIRST)
310 scoreboard_selected_panel = SB_PANEL_MAX;
314 ++scoreboard_selected_panel;
315 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
316 ++scoreboard_selected_panel;
317 if (scoreboard_selected_panel > SB_PANEL_MAX)
318 scoreboard_selected_panel = SB_PANEL_FIRST;
321 scoreboard_selected_panel_time = time;
323 else if(nPrimary == K_DOWNARROW)
327 LABEL(downarrow_action);
328 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
330 if (scoreboard_ui_enabled == 2)
332 entity curr_team = NULL;
333 bool scoreboard_selected_team_found = false;
334 if (!scoreboard_selected_team)
335 scoreboard_selected_team_found = true;
337 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
339 if(tm.team == NUM_SPECTATOR)
342 if (scoreboard_selected_team_found)
344 if (scoreboard_selected_team == tm)
345 scoreboard_selected_team_found = true;
348 if (curr_team == scoreboard_selected_team) // loop reached the last team
350 scoreboard_selected_team = curr_team;
355 entity curr_pl = NULL;
356 bool scoreboard_selected_player_found = false;
357 if (!scoreboard_selected_player)
358 scoreboard_selected_player_found = true;
360 for(tm = teams.sort_next; tm; tm = tm.sort_next)
362 if(tm.team != NUM_SPECTATOR)
363 for(pl = players.sort_next; pl; pl = pl.sort_next)
365 if(pl.team != tm.team)
368 if (scoreboard_selected_player_found)
370 if (scoreboard_selected_player == pl)
371 scoreboard_selected_player_found = true;
375 if (curr_pl == scoreboard_selected_player) // loop reached the last player
377 scoreboard_selected_player = curr_pl;
381 else if(nPrimary == K_UPARROW)
385 LABEL(uparrow_action);
386 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
388 if (scoreboard_ui_enabled == 2)
390 entity prev_team = NULL;
391 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
393 if(tm.team == NUM_SPECTATOR)
395 if (tm == scoreboard_selected_team)
400 scoreboard_selected_team = prev_team;
404 entity prev_pl = NULL;
406 for(tm = teams.sort_next; tm; tm = tm.sort_next)
408 if(tm.team != NUM_SPECTATOR)
409 for(pl = players.sort_next; pl; pl = pl.sort_next)
411 if(pl.team != tm.team)
413 if (pl == scoreboard_selected_player)
419 scoreboard_selected_player = prev_pl;
423 else if(nPrimary == K_RIGHTARROW)
427 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
428 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
430 else if(nPrimary == K_LEFTARROW)
434 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
435 rankings_start_column = max(rankings_start_column - 1, 0);
437 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
441 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
443 if (scoreboard_ui_enabled == 2)
446 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
449 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
450 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
451 HUD_Scoreboard_UI_Disable();
453 else if (scoreboard_selected_player)
454 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
457 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
461 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
463 switch (scoreboard_selected_columns_layout)
466 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
468 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
469 scoreboard_selected_columns_layout = 1;
474 localcmd(sprintf("scoreboard_columns_set default\n"));
475 scoreboard_selected_columns_layout = 2;
478 localcmd(sprintf("scoreboard_columns_set all\n"));
479 scoreboard_selected_columns_layout = 0;
484 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
488 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
489 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
491 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
495 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
497 if (scoreboard_selected_player)
499 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
500 HUD_Scoreboard_UI_Disable();
504 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
508 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
510 if (scoreboard_selected_player)
511 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
514 else if(hit_con_bind || nPrimary == K_PAUSE)
520 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
521 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
523 void Scoreboard_InitScores()
527 ps_primary = ps_secondary = NULL;
528 ts_primary = ts_secondary = -1;
529 FOREACH(Scores, true, {
530 if(scores_flags(it) & SFL_NOT_SORTABLE)
532 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
533 if(f == SFL_SORT_PRIO_PRIMARY)
535 if(f == SFL_SORT_PRIO_SECONDARY)
538 if(ps_secondary == NULL)
539 ps_secondary = ps_primary;
541 for(i = 0; i < MAX_TEAMSCORE; ++i)
543 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
544 if(f == SFL_SORT_PRIO_PRIMARY)
546 if(f == SFL_SORT_PRIO_SECONDARY)
549 if(ts_secondary == -1)
550 ts_secondary = ts_primary;
552 Cmd_Scoreboard_SetFields(0);
556 void Scoreboard_UpdatePlayerTeams()
558 static float update_time;
559 if (time <= update_time)
566 for(pl = players.sort_next; pl; pl = pl.sort_next)
568 numplayers += pl.team != NUM_SPECTATOR;
570 int Team = entcs_GetScoreTeam(pl.sv_entnum);
571 if(SetTeam(pl, Team))
574 Scoreboard_UpdatePlayerPos(pl);
578 pl = players.sort_next;
583 print(strcat("PNUM: ", ftos(num), "\n"));
588 int Scoreboard_CompareScore(int vl, int vr, int f)
590 TC(int, vl); TC(int, vr); TC(int, f);
591 if(f & SFL_ZERO_IS_WORST)
593 if(vl == 0 && vr != 0)
595 if(vl != 0 && vr == 0)
599 return IS_INCREASING(f);
601 return IS_DECREASING(f);
605 float Scoreboard_ComparePlayerScores(entity left, entity right)
607 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
608 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
615 if(vl == NUM_SPECTATOR)
617 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
619 if(!left.gotscores && right.gotscores)
624 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
625 if (res >= 0) return res;
627 if (ps_secondary && ps_secondary != ps_primary)
629 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
630 if (res >= 0) return res;
633 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
634 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
635 if (res >= 0) return res;
638 if (left.sv_entnum < right.sv_entnum)
644 void Scoreboard_UpdatePlayerPos(entity player)
647 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
649 SORT_SWAP(player, ent);
651 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
653 SORT_SWAP(ent, player);
657 float Scoreboard_CompareTeamScores(entity left, entity right)
659 if(left.team == NUM_SPECTATOR)
661 if(right.team == NUM_SPECTATOR)
666 for(int i = -2; i < MAX_TEAMSCORE; ++i)
670 if (fld_idx == -1) fld_idx = ts_primary;
671 else if (ts_secondary == ts_primary) continue;
672 else fld_idx = ts_secondary;
677 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
680 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
681 if (r >= 0) return r;
684 if (left.team < right.team)
690 void Scoreboard_UpdateTeamPos(entity Team)
693 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
695 SORT_SWAP(Team, ent);
697 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
699 SORT_SWAP(ent, Team);
703 void Cmd_Scoreboard_Help()
705 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
706 LOG_HELP(_("Usage:"));
707 LOG_HELP("^2scoreboard_columns_set ^3default");
708 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
709 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
710 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
711 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
712 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
713 LOG_HELP(_("The following field names are recognized (case insensitive):"));
719 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
720 "of game types, then a slash, to make the field show up only in these\n"
721 "or in all but these game types. You can also specify 'all' as a\n"
722 "field to show all fields available for the current game mode."));
725 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
726 "include/exclude ALL teams/noteams game modes."));
729 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
730 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
731 "right of the vertical bar aligned to the right."));
732 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
733 "other gamemodes except DM."));
736 // NOTE: adding a gametype with ? to not warn for an optional field
737 // make sure it's excluded in a previous exclusive rule, if any
738 // otherwise the previous exclusive rule warns anyway
739 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
740 #define SCOREBOARD_DEFAULT_COLUMNS \
741 "ping pl fps name |" \
742 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
743 " -teams,lms/deaths +ft,tdm/deaths" \
745 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
746 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
747 " +tdm,ft,dom,ons,as/teamkills"\
748 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
749 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
750 " +lms/lives +lms/rank" \
751 " +kh/kckills +kh/losses +kh/caps" \
752 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
753 " +as/objectives +nb/faults +nb/goals" \
754 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
755 " +dom/ticks +dom/takes" \
756 " -lms,rc,cts,inv,nb/score"
758 void Cmd_Scoreboard_SetFields(int argc)
763 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
767 return; // do nothing, we don't know gametype and scores yet
769 // sbt_fields uses strunzone on the titles!
770 if(!sbt_field_title[0])
771 for(i = 0; i < MAX_SBT_FIELDS; ++i)
772 sbt_field_title[i] = strzone("(null)");
774 // TODO: re enable with gametype dependant cvars?
775 if(argc < 3) // no arguments provided
776 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
779 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
783 if(argv(2) == "default" || argv(2) == "expand_default")
785 if(argv(2) == "expand_default")
786 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
787 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
789 else if(argv(2) == "all" || argv(2) == "ALL")
791 string s = "ping pl name |"; // scores without label (not really scores)
794 // scores without label
795 s = strcat(s, " ", "sum");
796 s = strcat(s, " ", "kdratio");
797 s = strcat(s, " ", "frags");
799 FOREACH(Scores, true, {
801 if(it != ps_secondary)
802 if(scores_label(it) != "")
803 s = strcat(s, " ", scores_label(it));
805 if(ps_secondary != ps_primary)
806 s = strcat(s, " ", scores_label(ps_secondary));
807 s = strcat(s, " ", scores_label(ps_primary));
808 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
815 hud_fontsize = HUD_GetFontsize("hud_fontsize");
817 for(i = 1; i < argc - 1; ++i)
820 bool nocomplain = false;
821 if(substring(str, 0, 1) == "?")
824 str = substring(str, 1, strlen(str) - 1);
827 slash = strstrofs(str, "/", 0);
830 pattern = substring(str, 0, slash);
831 str = substring(str, slash + 1, strlen(str) - (slash + 1));
833 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
837 str = strtolower(str);
838 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
839 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
844 // fields without a label (not networked via the score system)
845 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
846 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
847 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
848 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
849 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
850 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
851 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
852 default: // fields with a label
854 // map alternative labels
855 if (str == "damage") str = "dmg";
856 if (str == "damagetaken") str = "dmgtaken";
858 FOREACH(Scores, true, {
859 if (str == strtolower(scores_label(it))) {
861 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
865 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
866 if(!nocomplain && str != "fps") // server can disable the fps field
867 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
869 strfree(sbt_field_title[sbt_num_fields]);
870 sbt_field_size[sbt_num_fields] = 0;
874 sbt_field[sbt_num_fields] = j;
877 if(j == ps_secondary)
878 have_secondary = true;
883 if(sbt_num_fields >= MAX_SBT_FIELDS)
887 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
889 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
890 have_secondary = true;
891 if(ps_primary == ps_secondary)
892 have_secondary = true;
893 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
895 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
899 strfree(sbt_field_title[sbt_num_fields]);
900 for(i = sbt_num_fields; i > 0; --i)
902 sbt_field_title[i] = sbt_field_title[i-1];
903 sbt_field_size[i] = sbt_field_size[i-1];
904 sbt_field[i] = sbt_field[i-1];
906 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
907 sbt_field[0] = SP_NAME;
909 LOG_INFO("fixed missing field 'name'");
913 strfree(sbt_field_title[sbt_num_fields]);
914 for(i = sbt_num_fields; i > 1; --i)
916 sbt_field_title[i] = sbt_field_title[i-1];
917 sbt_field_size[i] = sbt_field_size[i-1];
918 sbt_field[i] = sbt_field[i-1];
920 sbt_field_title[1] = strzone("|");
921 sbt_field[1] = SP_SEPARATOR;
922 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
924 LOG_INFO("fixed missing field '|'");
927 else if(!have_separator)
929 strcpy(sbt_field_title[sbt_num_fields], "|");
930 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
931 sbt_field[sbt_num_fields] = SP_SEPARATOR;
933 LOG_INFO("fixed missing field '|'");
937 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
938 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
939 sbt_field[sbt_num_fields] = ps_secondary;
941 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
945 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
946 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
947 sbt_field[sbt_num_fields] = ps_primary;
949 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
953 sbt_field[sbt_num_fields] = SP_END;
956 string Scoreboard_AddPlayerId(string pl_name, entity pl)
958 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
959 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
960 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
964 vector sbt_field_rgb;
965 string sbt_field_icon0;
966 string sbt_field_icon1;
967 string sbt_field_icon2;
968 vector sbt_field_icon0_rgb;
969 vector sbt_field_icon1_rgb;
970 vector sbt_field_icon2_rgb;
971 string Scoreboard_GetName(entity pl)
973 if(ready_waiting && pl.ready)
975 sbt_field_icon0 = "gfx/scoreboard/player_ready";
979 int f = entcs_GetClientColors(pl.sv_entnum);
981 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
982 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
983 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
984 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
985 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
988 return entcs_GetName(pl.sv_entnum);
991 int autocvar_hud_panel_scoreboard_ping_best = 0;
992 int autocvar_hud_panel_scoreboard_ping_medium = 70;
993 int autocvar_hud_panel_scoreboard_ping_high = 100;
994 int autocvar_hud_panel_scoreboard_ping_worst = 150;
995 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
996 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
997 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
998 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
999 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1000 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1001 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1002 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1003 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1004 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1005 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1006 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1007 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1009 float tmp, num, denom;
1012 sbt_field_rgb = '1 1 1';
1013 sbt_field_icon0 = "";
1014 sbt_field_icon1 = "";
1015 sbt_field_icon2 = "";
1016 sbt_field_icon0_rgb = '1 1 1';
1017 sbt_field_icon1_rgb = '1 1 1';
1018 sbt_field_icon2_rgb = '1 1 1';
1019 int rounds_played = 0;
1021 rounds_played = pl.(scores(SP_ROUNDS_PL));
1026 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1027 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1032 sbt_field_rgb = COLOR_BEST;
1033 else if(f < PING_MED)
1034 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1035 else if(f < PING_HIGH)
1036 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1037 else if(f < PING_WORST)
1038 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1040 sbt_field_rgb = COLOR_WORST;
1046 f = pl.ping_packetloss;
1047 tmp = pl.ping_movementloss;
1048 if(f == 0 && tmp == 0)
1050 str = ftos(ceil(f * 100));
1052 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1053 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1054 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1058 str = Scoreboard_GetName(pl);
1059 if (autocvar_hud_panel_scoreboard_playerid)
1060 str = Scoreboard_AddPlayerId(str, pl);
1064 f = pl.(scores(SP_KILLS));
1065 f -= pl.(scores(SP_SUICIDES));
1067 return sprintf("%.1f", f / rounds_played);
1071 num = pl.(scores(SP_KILLS));
1072 denom = pl.(scores(SP_DEATHS));
1075 sbt_field_rgb = '0 1 0';
1077 str = sprintf("%.1f", num / rounds_played);
1079 str = sprintf("%d", num);
1080 } else if(num <= 0) {
1081 sbt_field_rgb = '1 0 0';
1083 str = sprintf("%.2f", num / (denom * rounds_played));
1085 str = sprintf("%.1f", num / denom);
1089 str = sprintf("%.2f", num / (denom * rounds_played));
1091 str = sprintf("%.1f", num / denom);
1096 f = pl.(scores(SP_KILLS));
1097 f -= pl.(scores(SP_DEATHS));
1100 sbt_field_rgb = '0 1 0';
1102 sbt_field_rgb = '1 1 1';
1104 sbt_field_rgb = '1 0 0';
1107 return sprintf("%.1f", f / rounds_played);
1112 float elo = pl.(scores(SP_ELO));
1114 case -1: return "...";
1115 case -2: return _("N/A");
1116 default: return ftos(elo);
1122 float fps = pl.(scores(SP_FPS));
1125 sbt_field_rgb = '1 1 1';
1126 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1128 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1129 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1134 return ftos(pl.(scores(field)));
1136 case SP_DMG: case SP_DMGTAKEN:
1138 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1139 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1141 default: case SP_SCORE:
1142 tmp = pl.(scores(field));
1143 f = scores_flags(field);
1144 if(field == ps_primary)
1145 sbt_field_rgb = '1 1 0';
1146 else if(field == ps_secondary)
1147 sbt_field_rgb = '0 1 1';
1149 sbt_field_rgb = '1 1 1';
1150 return ScoreString(f, tmp, rounds_played);
1155 float sbt_fixcolumnwidth_len;
1156 float sbt_fixcolumnwidth_iconlen;
1157 float sbt_fixcolumnwidth_marginlen;
1159 string Scoreboard_FixColumnWidth(int i, string str)
1165 sbt_fixcolumnwidth_iconlen = 0;
1167 if(sbt_field_icon0 != "")
1169 sz = draw_getimagesize(sbt_field_icon0);
1171 if(sbt_fixcolumnwidth_iconlen < f)
1172 sbt_fixcolumnwidth_iconlen = f;
1175 if(sbt_field_icon1 != "")
1177 sz = draw_getimagesize(sbt_field_icon1);
1179 if(sbt_fixcolumnwidth_iconlen < f)
1180 sbt_fixcolumnwidth_iconlen = f;
1183 if(sbt_field_icon2 != "")
1185 sz = draw_getimagesize(sbt_field_icon2);
1187 if(sbt_fixcolumnwidth_iconlen < f)
1188 sbt_fixcolumnwidth_iconlen = f;
1191 if(sbt_fixcolumnwidth_iconlen != 0)
1193 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1194 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1197 sbt_fixcolumnwidth_marginlen = 0;
1199 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1202 float remaining_space = 0;
1203 for(j = 0; j < sbt_num_fields; ++j)
1205 if (sbt_field[i] != SP_SEPARATOR)
1206 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1207 sbt_field_size[i] = panel_size.x - remaining_space;
1209 if (sbt_fixcolumnwidth_iconlen != 0)
1210 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1211 float namesize = panel_size.x - remaining_space;
1212 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1213 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1215 max_namesize = vid_conwidth - remaining_space;
1218 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1220 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1221 if(sbt_field_size[i] < f)
1222 sbt_field_size[i] = f;
1227 void Scoreboard_initFieldSizes()
1229 for(int i = 0; i < sbt_num_fields; ++i)
1231 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1232 Scoreboard_FixColumnWidth(i, "");
1236 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1239 vector column_dim = eY * panel_size.y;
1241 column_dim.y -= 1.25 * hud_fontsize.y;
1242 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1243 pos.x += hud_fontsize.x * 0.5;
1244 for(i = 0; i < sbt_num_fields; ++i)
1246 if(sbt_field[i] == SP_SEPARATOR)
1248 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1251 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1252 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1253 pos.x += column_dim.x;
1255 if(sbt_field[i] == SP_SEPARATOR)
1257 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1258 for(i = sbt_num_fields - 1; i > 0; --i)
1260 if(sbt_field[i] == SP_SEPARATOR)
1263 pos.x -= sbt_field_size[i];
1268 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1269 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1272 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1273 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1274 pos.x -= hud_fontsize.x;
1278 pos.x = panel_pos.x;
1279 pos.y += 1.25 * hud_fontsize.y;
1283 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1285 TC(bool, is_self); TC(int, pl_number);
1287 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1289 vector h_pos = item_pos;
1290 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1291 // alternated rows highlighting
1292 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1294 if (pl == scoreboard_selected_player)
1295 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1298 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1299 else if((sbt_highlight) && (!(pl_number % 2)))
1300 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1302 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1304 vector pos = item_pos;
1305 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1307 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1309 pos.x += hud_fontsize.x * 0.5;
1310 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1311 vector tmp = '0 0 0';
1313 PlayerScoreField field;
1314 for(i = 0; i < sbt_num_fields; ++i)
1316 field = sbt_field[i];
1317 if(field == SP_SEPARATOR)
1320 if(is_spec && field != SP_NAME && field != SP_PING) {
1321 pos.x += sbt_field_size[i] + hud_fontsize.x;
1324 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1325 str = Scoreboard_FixColumnWidth(i, str);
1327 pos.x += sbt_field_size[i] + hud_fontsize.x;
1329 if(field == SP_NAME) {
1330 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1331 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1333 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1334 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1337 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1338 if(sbt_field_icon0 != "")
1339 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1340 if(sbt_field_icon1 != "")
1341 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1342 if(sbt_field_icon2 != "")
1343 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1346 if(sbt_field[i] == SP_SEPARATOR)
1348 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1349 for(i = sbt_num_fields-1; i > 0; --i)
1351 field = sbt_field[i];
1352 if(field == SP_SEPARATOR)
1355 if(is_spec && field != SP_NAME && field != SP_PING) {
1356 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1360 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1361 str = Scoreboard_FixColumnWidth(i, str);
1363 if(field == SP_NAME) {
1364 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1365 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1367 tmp.x = sbt_fixcolumnwidth_len;
1368 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1371 tmp.x = sbt_field_size[i];
1372 if(sbt_field_icon0 != "")
1373 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1374 if(sbt_field_icon1 != "")
1375 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1376 if(sbt_field_icon2 != "")
1377 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1378 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1383 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1386 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1389 vector h_pos = item_pos;
1390 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1392 bool complete = (this_team == NUM_SPECTATOR);
1395 if((sbt_highlight) && (!(pl_number % 2)))
1396 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1398 vector pos = item_pos;
1399 pos.x += hud_fontsize.x * 0.5;
1400 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1402 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1404 width_limit -= stringwidth("...", false, hud_fontsize);
1405 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1406 static float max_name_width = 0;
1408 float fieldsize = 0;
1409 float min_fieldsize = 0;
1410 float fieldpadding = hud_fontsize.x * 0.25;
1411 if(this_team == NUM_SPECTATOR)
1413 if(autocvar_hud_panel_scoreboard_spectators_showping)
1414 min_fieldsize = stringwidth("999", false, hud_fontsize);
1416 else if(autocvar_hud_panel_scoreboard_others_showscore)
1417 min_fieldsize = stringwidth("99", false, hud_fontsize);
1418 for(i = 0; pl; pl = pl.sort_next)
1420 if(pl.team != this_team)
1422 if(pl == ignored_pl)
1426 if(this_team == NUM_SPECTATOR)
1428 if(autocvar_hud_panel_scoreboard_spectators_showping)
1429 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1431 else if(autocvar_hud_panel_scoreboard_others_showscore)
1432 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1434 string str = entcs_GetName(pl.sv_entnum);
1435 if (autocvar_hud_panel_scoreboard_playerid)
1436 str = Scoreboard_AddPlayerId(str, pl);
1437 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1438 float column_width = stringwidth(str, true, hud_fontsize);
1439 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1441 if(column_width > max_name_width)
1442 max_name_width = column_width;
1443 column_width = max_name_width;
1447 fieldsize = stringwidth(field, false, hud_fontsize);
1448 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1451 if(pos.x + column_width > width_limit)
1456 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1461 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1462 pos.y += hud_fontsize.y * 1.25;
1466 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1468 if (pl == scoreboard_selected_player)
1470 h_size.x = column_width + hud_fontsize.x * 0.25;
1471 h_size.y = hud_fontsize.y;
1472 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1476 vector name_pos = pos;
1477 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1478 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1479 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1482 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1483 h_size.y = hud_fontsize.y;
1484 vector field_pos = pos;
1485 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1486 field_pos.x += column_width - h_size.x;
1488 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1489 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1490 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1494 h_size.x = column_width + hud_fontsize.x * 0.25;
1495 h_size.y = hud_fontsize.y;
1496 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1498 pos.x += column_width;
1499 pos.x += hud_fontsize.x;
1501 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1504 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1506 int max_players = 999;
1507 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1509 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1512 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1513 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1514 height /= team_count;
1517 height -= panel_bg_padding * 2; // - padding
1518 max_players = floor(height / (hud_fontsize.y * 1.25));
1519 if(max_players <= 1)
1521 if(max_players == tm.team_size)
1526 entity me = playerslots[current_player];
1528 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1529 panel_size.y += panel_bg_padding * 2;
1531 vector scoreboard_selected_hl_pos = pos;
1532 vector scoreboard_selected_hl_size = '0 0 0';
1533 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1534 scoreboard_selected_hl_size.y = panel_size.y;
1538 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1539 if(panel.current_panel_bg != "0")
1540 end_pos.y += panel_bg_border * 2;
1542 if(panel_bg_padding)
1544 panel_pos += '1 1 0' * panel_bg_padding;
1545 panel_size -= '2 2 0' * panel_bg_padding;
1549 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1553 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1555 pos.y += 1.25 * hud_fontsize.y;
1558 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1560 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1563 // print header row and highlight columns
1564 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1566 // fill the table and draw the rows
1567 bool is_self = false;
1568 bool self_shown = false;
1570 for(pl = players.sort_next; pl; pl = pl.sort_next)
1572 if(pl.team != tm.team)
1574 if(i == max_players - 2 && pl != me)
1576 if(!self_shown && me.team == tm.team)
1578 Scoreboard_DrawItem(pos, rgb, me, true, i);
1580 pos.y += 1.25 * hud_fontsize.y;
1584 if(i >= max_players - 1)
1586 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1589 is_self = (pl.sv_entnum == current_player);
1590 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1593 pos.y += 1.25 * hud_fontsize.y;
1597 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1599 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1601 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1602 _alpha *= panel_fg_alpha;
1604 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1608 panel_size.x += panel_bg_padding * 2; // restore initial width
1612 bool Scoreboard_WouldDraw()
1614 if (scoreboard_ui_enabled)
1616 if (scoreboard_ui_disabling)
1618 if (scoreboard_fade_alpha == 0)
1619 HUD_Scoreboard_UI_Disable_Instantly();
1622 if (intermission && scoreboard_ui_enabled == 2)
1624 HUD_Scoreboard_UI_Disable_Instantly();
1629 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1631 else if (QuickMenu_IsOpened())
1633 else if (HUD_Radar_Clickable())
1635 else if (sb_showscores) // set by +showscores engine command
1637 else if (intermission == 1)
1639 else if (intermission == 2)
1641 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1642 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1646 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1651 float average_accuracy;
1652 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1654 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1656 WepSet weapons_stat = WepSet_GetFromStat();
1657 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1658 int disownedcnt = 0;
1660 FOREACH(Weapons, it != WEP_Null, {
1661 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1663 WepSet set = it.m_wepset;
1664 if(it.spawnflags & WEP_TYPE_OTHER)
1669 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1671 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1678 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1679 if (weapon_cnt <= 0) return pos;
1682 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1684 int columns = ceil(weapon_cnt / rows);
1686 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1687 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1688 float height = weapon_height + hud_fontsize.y;
1690 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);
1691 pos.y += 1.25 * hud_fontsize.y;
1692 if(panel.current_panel_bg != "0")
1693 pos.y += panel_bg_border;
1696 panel_size.y = height * rows;
1697 panel_size.y += panel_bg_padding * 2;
1699 float panel_bg_alpha_save = panel_bg_alpha;
1700 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1702 panel_bg_alpha = panel_bg_alpha_save;
1704 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1705 if(panel.current_panel_bg != "0")
1706 end_pos.y += panel_bg_border * 2;
1708 if(panel_bg_padding)
1710 panel_pos += '1 1 0' * panel_bg_padding;
1711 panel_size -= '2 2 0' * panel_bg_padding;
1715 vector tmp = panel_size;
1717 float weapon_width = tmp.x / columns / rows;
1720 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1724 // column highlighting
1725 for (int i = 0; i < columns; ++i)
1727 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);
1730 for (int i = 0; i < rows; ++i)
1731 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1734 average_accuracy = 0;
1735 int weapons_with_stats = 0;
1737 pos.x += weapon_width / 2;
1739 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1742 Accuracy_LoadColors();
1744 float oldposx = pos.x;
1748 FOREACH(Weapons, it != WEP_Null, {
1749 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1751 WepSet set = it.m_wepset;
1752 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1754 if (it.spawnflags & WEP_TYPE_OTHER)
1758 if (weapon_stats >= 0)
1759 weapon_alpha = sbt_fg_alpha;
1761 weapon_alpha = 0.2 * sbt_fg_alpha;
1764 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1766 if (weapon_stats >= 0) {
1767 weapons_with_stats += 1;
1768 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1770 string s = sprintf("%d%%", weapon_stats * 100);
1771 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1773 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1774 rgb = Accuracy_GetColor(weapon_stats);
1776 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1778 tmpos.x += weapon_width * rows;
1779 pos.x += weapon_width * rows;
1780 if (rows == 2 && column == columns - 1) {
1788 if (weapons_with_stats)
1789 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1791 panel_size.x += panel_bg_padding * 2; // restore initial width
1796 bool is_item_filtered(entity it)
1798 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1800 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1803 if (it.instanceOfArmor || it.instanceOfHealth)
1805 int ha_mask = floor(mask) % 10;
1808 default: return false;
1809 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1810 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1811 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1812 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1815 if (it.instanceOfAmmo)
1817 int ammo_mask = floor(mask / 10) % 10;
1818 return (ammo_mask == 1);
1823 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1825 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1827 int disowned_cnt = 0;
1828 int uninteresting_cnt = 0;
1829 IL_EACH(default_order_items, true, {
1830 int q = g_inventory.inv_items[it.m_id];
1831 //q = 1; // debug: display all items
1832 if (is_item_filtered(it))
1833 ++uninteresting_cnt;
1837 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1838 int n = items_cnt - disowned_cnt;
1839 if (n <= 0) return pos;
1841 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1842 int columns = max(6, ceil(n / rows));
1844 float item_height = hud_fontsize.y * 2.3;
1845 float height = item_height + hud_fontsize.y;
1847 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1848 pos.y += 1.25 * hud_fontsize.y;
1849 if(panel.current_panel_bg != "0")
1850 pos.y += panel_bg_border;
1853 panel_size.y = height * rows;
1854 panel_size.y += panel_bg_padding * 2;
1856 float panel_bg_alpha_save = panel_bg_alpha;
1857 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1859 panel_bg_alpha = panel_bg_alpha_save;
1861 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1862 if(panel.current_panel_bg != "0")
1863 end_pos.y += panel_bg_border * 2;
1865 if(panel_bg_padding)
1867 panel_pos += '1 1 0' * panel_bg_padding;
1868 panel_size -= '2 2 0' * panel_bg_padding;
1872 vector tmp = panel_size;
1874 float item_width = tmp.x / columns / rows;
1877 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1881 // column highlighting
1882 for (int i = 0; i < columns; ++i)
1884 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);
1887 for (int i = 0; i < rows; ++i)
1888 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1892 pos.x += item_width / 2;
1894 float oldposx = pos.x;
1898 IL_EACH(default_order_items, !is_item_filtered(it), {
1899 int n = g_inventory.inv_items[it.m_id];
1900 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1901 if (n <= 0) continue;
1902 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);
1904 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1905 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1906 tmpos.x += item_width * rows;
1907 pos.x += item_width * rows;
1908 if (rows == 2 && column == columns - 1) {
1916 panel_size.x += panel_bg_padding * 2; // restore initial width
1921 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1923 pos.x += hud_fontsize.x * 0.25;
1924 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1925 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1926 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1928 pos.y += hud_fontsize.y;
1933 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1934 float stat_secrets_found, stat_secrets_total;
1935 float stat_monsters_killed, stat_monsters_total;
1939 // get monster stats
1940 stat_monsters_killed = STAT(MONSTERS_KILLED);
1941 stat_monsters_total = STAT(MONSTERS_TOTAL);
1943 // get secrets stats
1944 stat_secrets_found = STAT(SECRETS_FOUND);
1945 stat_secrets_total = STAT(SECRETS_TOTAL);
1947 // get number of rows
1948 if(stat_secrets_total)
1950 if(stat_monsters_total)
1953 // if no rows, return
1957 // draw table header
1958 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1959 pos.y += 1.25 * hud_fontsize.y;
1960 if(panel.current_panel_bg != "0")
1961 pos.y += panel_bg_border;
1964 panel_size.y = hud_fontsize.y * rows;
1965 panel_size.y += panel_bg_padding * 2;
1968 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1969 if(panel.current_panel_bg != "0")
1970 end_pos.y += panel_bg_border * 2;
1972 if(panel_bg_padding)
1974 panel_pos += '1 1 0' * panel_bg_padding;
1975 panel_size -= '2 2 0' * panel_bg_padding;
1979 vector tmp = panel_size;
1982 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1985 if(stat_monsters_total)
1987 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1988 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1992 if(stat_secrets_total)
1994 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1995 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1998 panel_size.x += panel_bg_padding * 2; // restore initial width
2002 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2005 RANKINGS_RECEIVED_CNT = 0;
2006 for (i=RANKINGS_CNT-1; i>=0; --i)
2008 ++RANKINGS_RECEIVED_CNT;
2010 if (RANKINGS_RECEIVED_CNT == 0)
2013 vector hl_rgb = rgb + '0.5 0.5 0.5';
2015 vector scoreboard_selected_hl_pos = pos;
2017 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2018 pos.y += 1.25 * hud_fontsize.y;
2019 if(panel.current_panel_bg != "0")
2020 pos.y += panel_bg_border;
2022 vector scoreboard_selected_hl_size = '0 0 0';
2023 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2024 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2029 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2031 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2036 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2038 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2042 float ranksize = 3 * hud_fontsize.x;
2043 float timesize = 5 * hud_fontsize.x;
2044 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2045 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2046 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2049 rankings_cnt = RANKINGS_RECEIVED_CNT;
2050 rankings_rows = ceil(rankings_cnt / rankings_columns);
2053 // expand name column to fill the entire row
2054 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2055 namesize += available_space;
2056 columnsize.x += available_space;
2058 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2059 panel_size.y += panel_bg_padding * 2;
2060 scoreboard_selected_hl_size.y += panel_size.y;
2064 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2065 if(panel.current_panel_bg != "0")
2066 end_pos.y += panel_bg_border * 2;
2068 if(panel_bg_padding)
2070 panel_pos += '1 1 0' * panel_bg_padding;
2071 panel_size -= '2 2 0' * panel_bg_padding;
2077 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2079 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2081 int column = 0, j = 0;
2082 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2083 int start_item = rankings_start_column * rankings_rows;
2084 for(i = start_item; i < start_item + rankings_cnt; ++i)
2086 int t = grecordtime[i];
2090 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2091 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2092 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2093 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2095 str = count_ordinal(i+1);
2096 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2097 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2098 str = ColorTranslateRGB(grecordholder[i]);
2100 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2101 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2103 pos.y += 1.25 * hud_fontsize.y;
2105 if(j >= rankings_rows)
2109 pos.x += panel_size.x / rankings_columns;
2110 pos.y = panel_pos.y;
2113 strfree(zoned_name_self);
2115 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2117 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2118 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2121 panel_size.x += panel_bg_padding * 2; // restore initial width
2125 bool have_weapon_stats;
2126 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2128 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2130 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2133 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2134 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2140 if (!have_weapon_stats)
2142 FOREACH(Weapons, it != WEP_Null, {
2143 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2144 if (weapon_stats >= 0)
2146 have_weapon_stats = true;
2150 if (!have_weapon_stats)
2157 bool have_item_stats;
2158 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2160 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2162 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2165 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2166 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2172 if (!have_item_stats)
2174 IL_EACH(default_order_items, true, {
2175 if (!is_item_filtered(it))
2177 int q = g_inventory.inv_items[it.m_id];
2178 //q = 1; // debug: display all items
2181 have_item_stats = true;
2186 if (!have_item_stats)
2193 vector Scoreboard_Spectators_Draw(vector pos) {
2198 for(pl = players.sort_next; pl; pl = pl.sort_next)
2200 if(pl.team == NUM_SPECTATOR)
2202 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2203 if(tm.team == NUM_SPECTATOR)
2205 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2206 draw_beginBoldFont();
2207 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2209 pos.y += 1.25 * hud_fontsize.y;
2211 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2212 pos.y += 1.25 * hud_fontsize.y;
2217 if (str != "") // if there's at least one spectator
2218 pos.y += 0.5 * hud_fontsize.y;
2223 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2225 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2226 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2227 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2228 (s_label == "score") ? CTX(_("SCO^points")) :
2229 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2232 void Scoreboard_Draw()
2234 if(!autocvar__hud_configure)
2236 if(!hud_draw_maximized) return;
2238 // frametime checks allow to toggle the scoreboard even when the game is paused
2239 if(scoreboard_active) {
2240 if (scoreboard_fade_alpha == 0)
2241 scoreboard_time = time;
2242 if(hud_configure_menu_open == 1)
2243 scoreboard_fade_alpha = 1;
2244 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2245 if (scoreboard_fadeinspeed && frametime)
2246 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2248 scoreboard_fade_alpha = 1;
2249 if(hud_fontsize_str != autocvar_hud_fontsize)
2251 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2252 Scoreboard_initFieldSizes();
2253 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2257 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2258 if (scoreboard_fadeoutspeed && frametime)
2259 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2261 scoreboard_fade_alpha = 0;
2264 if (!scoreboard_fade_alpha)
2266 scoreboard_acc_fade_alpha = 0;
2267 scoreboard_itemstats_fade_alpha = 0;
2272 scoreboard_fade_alpha = 0;
2274 if (autocvar_hud_panel_scoreboard_dynamichud)
2277 HUD_Scale_Disable();
2279 if(scoreboard_fade_alpha <= 0)
2281 panel_fade_alpha *= scoreboard_fade_alpha;
2282 HUD_Panel_LoadCvars();
2284 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2285 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2286 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2287 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2288 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2289 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2290 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2292 // don't overlap with con_notify
2293 if(!autocvar__hud_configure)
2294 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2296 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2297 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2298 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2299 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2300 panel_pos.x = scoreboard_left;
2301 panel_size.x = fixed_scoreboard_width;
2303 Scoreboard_UpdatePlayerTeams();
2305 scoreboard_top = panel_pos.y;
2306 vector pos = panel_pos;
2311 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2313 // Begin of Game Info Section
2314 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2315 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2317 // Game Info: Game Type
2318 if (scoreboard_ui_enabled == 2)
2319 str = _("Team Selection");
2321 str = MapInfo_Type_ToText(gametype);
2322 draw_beginBoldFont();
2323 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);
2326 pos.y += sb_gameinfo_type_fontsize.y;
2327 // Game Info: Game Detail
2328 if (scoreboard_ui_enabled == 2)
2330 if (scoreboard_selected_team)
2331 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2333 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2334 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2336 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2337 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2338 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2342 float tl = STAT(TIMELIMIT);
2343 float fl = STAT(FRAGLIMIT);
2344 float ll = STAT(LEADLIMIT);
2345 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2348 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2349 if(!gametype.m_hidelimits)
2354 str = strcat(str, "^7 / "); // delimiter
2355 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2359 if(tl > 0 || fl > 0)
2362 if (ll_and_fl && fl > 0)
2363 str = strcat(str, "^7 & ");
2365 str = strcat(str, "^7 / ");
2367 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2370 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
2371 // map name and player count
2375 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2376 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2377 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2379 // End of Game Info Section
2381 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2382 if(panel.current_panel_bg != "0")
2383 pos.y += panel_bg_border;
2385 // Draw the scoreboard
2386 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2389 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2393 vector panel_bg_color_save = panel_bg_color;
2394 vector team_score_baseoffset;
2395 vector team_size_baseoffset;
2396 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2398 // put team score to the left of scoreboard (and team size to the right)
2399 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2400 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2401 if(panel.current_panel_bg != "0")
2403 team_score_baseoffset.x -= panel_bg_border;
2404 team_size_baseoffset.x += panel_bg_border;
2409 // put team score to the right of scoreboard (and team size to the left)
2410 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2411 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2412 if(panel.current_panel_bg != "0")
2414 team_score_baseoffset.x += panel_bg_border;
2415 team_size_baseoffset.x -= panel_bg_border;
2419 int team_size_total = 0;
2420 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2422 // calculate team size total (sum of all team sizes)
2423 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2424 if(tm.team != NUM_SPECTATOR)
2425 team_size_total += tm.team_size;
2428 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2430 if(tm.team == NUM_SPECTATOR)
2435 draw_beginBoldFont();
2436 vector rgb = Team_ColorRGB(tm.team);
2437 str = ftos(tm.(teamscores(ts_primary)));
2438 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2440 // team score on the left (default)
2441 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2445 // team score on the right
2446 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2448 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2450 // team size (if set to show on the side)
2451 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2453 // calculate the starting position for the whole team size info string
2454 str = sprintf("%d/%d", tm.team_size, team_size_total);
2455 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2457 // team size on the left
2458 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2462 // team size on the right
2463 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2465 str = sprintf("%d", tm.team_size);
2466 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2467 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2468 str = sprintf("/%d", team_size_total);
2469 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2473 // secondary score, e.g. keyhunt
2474 if(ts_primary != ts_secondary)
2476 str = ftos(tm.(teamscores(ts_secondary)));
2477 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2480 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2485 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2488 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2491 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2492 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2493 else if(panel_bg_color_team > 0)
2494 panel_bg_color = rgb * panel_bg_color_team;
2496 panel_bg_color = rgb;
2497 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2499 panel_bg_color = panel_bg_color_save;
2503 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2504 if(tm.team != NUM_SPECTATOR)
2507 // display it anyway
2508 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2511 // draw scoreboard spectators before accuracy and item stats
2512 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2513 pos = Scoreboard_Spectators_Draw(pos);
2516 // draw accuracy and item stats
2517 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2518 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2519 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2520 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2522 // draw scoreboard spectators after accuracy and item stats and before rankings
2523 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2524 pos = Scoreboard_Spectators_Draw(pos);
2527 if(MUTATOR_CALLHOOK(ShowRankings)) {
2528 string ranktitle = M_ARGV(0, string);
2529 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2530 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2531 if(race_speedaward_alltimebest)
2534 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2538 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2539 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2540 str = strcat(str, " / ");
2542 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2543 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2544 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2545 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2547 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2552 // draw scoreboard spectators after rankings
2553 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2554 pos = Scoreboard_Spectators_Draw(pos);
2557 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2559 // draw scoreboard spectators after mapstats
2560 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2561 pos = Scoreboard_Spectators_Draw(pos);
2565 // print information about respawn status
2566 float respawn_time = STAT(RESPAWN_TIME);
2567 if(!intermission && respawn_time)
2569 if(respawn_time < 0)
2571 // a negative number means we are awaiting respawn, time value is still the same
2572 respawn_time *= -1; // remove mark now that we checked it
2574 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2575 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2577 str = sprintf(_("^1Respawning in ^3%s^1..."),
2578 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2579 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2581 count_seconds(ceil(respawn_time - time))
2585 else if(time < respawn_time)
2587 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2588 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2589 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2591 count_seconds(ceil(respawn_time - time))
2595 else if(time >= respawn_time)
2596 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2598 pos.y += 1.2 * hud_fontsize.y;
2599 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2602 pos.y += hud_fontsize.y;
2603 if (scoreboard_fade_alpha < 1)
2604 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2605 else if (pos.y != scoreboard_bottom)
2607 if (pos.y > scoreboard_bottom)
2608 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2610 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2615 if (scoreboard_fade_alpha == 1)
2617 if (scoreboard_bottom > 0.95 * vid_conheight)
2618 rankings_rows = max(1, rankings_rows - 1);
2619 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2620 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2622 rankings_cnt = rankings_rows * rankings_columns;