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/gamemodes/gamemode/duel/duel.qh>
13 #include <common/minigames/cl_minigames.qh>
14 #include <common/net_linked.qh>
15 #include <common/scores.qh>
16 #include <common/stats.qh>
17 #include <common/teams.qh>
18 #include <common/items/inventory.qh>
22 void Scoreboard_Draw_Export(int fh)
24 // allow saving cvars that aesthetically change the panel into hud skin files
25 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
26 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
27 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
34 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
35 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
36 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
37 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
38 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
42 const int MAX_SBT_FIELDS = MAX_SCORE;
44 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
45 float sbt_field_size[MAX_SBT_FIELDS + 1];
46 string sbt_field_title[MAX_SBT_FIELDS + 1];
49 string autocvar_hud_fontsize;
50 string hud_fontsize_str;
53 vector duel_score_fontsize;
54 vector duel_name_fontsize;
55 vector duel_score_size;
56 vector team_score_fontsize;
57 vector team_name_fontsize;
58 vector team_score_size;
61 float autocvar_hud_panel_scoreboard_duel_weapon_scale = 1.25; // z411
65 float sbt_fg_alpha_self;
67 float sbt_highlight_alpha;
68 float sbt_highlight_alpha_self;
69 float sbt_highlight_alpha_eliminated;
71 // provide basic panel cvars to old clients
72 // TODO remove them after a future release (0.8.2+)
73 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
74 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
75 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
76 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
77 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
78 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
79 noref string autocvar_hud_panel_scoreboard_bg_border = "";
80 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
82 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
83 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
84 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
85 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
86 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
87 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
88 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
89 bool autocvar_hud_panel_scoreboard_table_highlight = true;
90 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
91 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
92 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
93 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
94 float autocvar_hud_panel_scoreboard_team_size_position = 0;
95 float autocvar_hud_panel_scoreboard_spectators_position = 1;
97 bool autocvar_hud_panel_scoreboard_accuracy = true;
98 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
99 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
100 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
101 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
103 bool autocvar_hud_panel_scoreboard_itemstats = true;
104 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
105 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
106 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
107 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
108 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
110 bool autocvar_hud_panel_scoreboard_dynamichud = false;
112 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
113 bool autocvar_hud_panel_scoreboard_others_showscore = true;
114 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
115 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
116 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
117 bool autocvar_hud_panel_scoreboard_playerid = false;
118 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
119 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
120 bool autocvar_hud_panel_scoreboard_scores_per_round;
122 int average_ping[NUM_TEAMS];
125 float scoreboard_time;
129 if(autocvar_hud_panel_scoreboard_scores_per_round)
130 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
133 // mode 0: returns translated label
134 // mode 1: prints name and description of all the labels
135 string Label_getInfo(string label, int mode)
138 label = "bckills"; // first case in the switch
142 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
143 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
144 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")));
145 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
146 case "cn": if (!mode) return CTX(_("SCO^cn")); else LOG_HELP(strcat("^3", "cn", " ^7", _("Player country"))); //LegendGuard adds cn for Country column 05-04-2021
147 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
148 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
149 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
150 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
151 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
152 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
153 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
154 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
155 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
156 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
157 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
158 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
159 case "hunts": if (!mode) return CTX(_("SCO^hunts")); else LOG_HELP(strcat("^3", "hunts", " ^7", _("Number of hunts (Survival)")));
160 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
161 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
162 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
163 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
164 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
165 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
166 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
167 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
168 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
169 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
170 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
171 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")));
172 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
173 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
174 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
175 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
176 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
177 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
178 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
179 case "rounds_pl": if (!mode) return CTX(_("SCO^rounds played"));else LOG_HELP(strcat("^3", "rounds_pl", " ^7", _("Number of rounds played")));
180 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
181 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
182 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
183 case "survivals": if (!mode) return CTX(_("SCO^survivals")); else LOG_HELP(strcat("^3", "survivals", " ^7", _("Number of survivals")));
184 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
185 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
186 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
187 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
188 default: return label;
193 bool scoreboard_ui_disabling;
194 void HUD_Scoreboard_UI_Disable()
196 scoreboard_ui_disabling = true;
197 sb_showscores = false;
200 void HUD_Scoreboard_UI_Disable_Instantly()
202 scoreboard_ui_disabling = false;
203 scoreboard_ui_enabled = 0;
204 scoreboard_selected_panel = 0;
205 scoreboard_selected_player = NULL;
206 scoreboard_selected_team = NULL;
209 // mode: 0 normal, 1 team selection
210 void Scoreboard_UI_Enable(int mode)
216 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
219 // release player's pressed keys as they aren't released elsewhere
220 // in particular jump needs to be released as it may open the team selection
221 // (when server detects jump has been pressed it sends the command to open the team selection)
222 Release_Common_Keys();
223 scoreboard_ui_enabled = 2;
224 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
228 if (scoreboard_ui_enabled == 1)
230 scoreboard_ui_enabled = 1;
231 scoreboard_selected_panel = SB_PANEL_FIRST;
233 scoreboard_selected_player = NULL;
234 scoreboard_selected_team = NULL;
235 scoreboard_selected_panel_time = time;
238 int rankings_start_column;
239 int rankings_rows = 0;
240 int rankings_columns = 0;
241 int rankings_cnt = 0;
242 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
246 if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
251 mousepos.x = nPrimary;
252 mousepos.y = nSecondary;
259 // at this point bInputType can only be 0 or 1 (key pressed or released)
260 bool key_pressed = (bInputType == 0);
262 // ESC to exit (TAB-ESC works too)
263 if(nPrimary == K_ESCAPE)
267 HUD_Scoreboard_UI_Disable();
271 // block any input while a menu dialog is fading
272 if(autocvar__menu_alpha)
278 // allow console bind to work
279 string con_keys = findkeysforcommand("toggleconsole", 0);
280 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
282 bool hit_con_bind = false;
284 for (i = 0; i < keys; ++i)
286 if(nPrimary == stof(argv(i)))
291 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
292 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
293 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
294 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
297 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
298 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
299 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
300 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
303 if(nPrimary == K_TAB)
307 if (scoreboard_ui_enabled == 2)
309 if (hudShiftState & S_SHIFT)
312 goto downarrow_action;
315 if (hudShiftState & S_SHIFT)
317 --scoreboard_selected_panel;
318 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
319 --scoreboard_selected_panel;
320 if (scoreboard_selected_panel < SB_PANEL_FIRST)
321 scoreboard_selected_panel = SB_PANEL_MAX;
325 ++scoreboard_selected_panel;
326 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
327 ++scoreboard_selected_panel;
328 if (scoreboard_selected_panel > SB_PANEL_MAX)
329 scoreboard_selected_panel = SB_PANEL_FIRST;
332 scoreboard_selected_panel_time = time;
334 else if(nPrimary == K_DOWNARROW)
338 LABEL(downarrow_action);
339 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
341 if (scoreboard_ui_enabled == 2)
343 entity curr_team = NULL;
344 bool scoreboard_selected_team_found = false;
345 if (!scoreboard_selected_team)
346 scoreboard_selected_team_found = true;
348 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
350 if(tm.team == NUM_SPECTATOR)
353 if (scoreboard_selected_team_found)
355 if (scoreboard_selected_team == tm)
356 scoreboard_selected_team_found = true;
359 if (curr_team == scoreboard_selected_team) // loop reached the last team
361 scoreboard_selected_team = curr_team;
366 entity curr_pl = NULL;
367 bool scoreboard_selected_player_found = false;
368 if (!scoreboard_selected_player)
369 scoreboard_selected_player_found = true;
371 for(tm = teams.sort_next; tm; tm = tm.sort_next)
373 if(tm.team != NUM_SPECTATOR)
374 for(pl = players.sort_next; pl; pl = pl.sort_next)
376 if(pl.team != tm.team)
379 if (scoreboard_selected_player_found)
381 if (scoreboard_selected_player == pl)
382 scoreboard_selected_player_found = true;
386 if (curr_pl == scoreboard_selected_player) // loop reached the last player
388 scoreboard_selected_player = curr_pl;
392 else if(nPrimary == K_UPARROW)
396 LABEL(uparrow_action);
397 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
399 if (scoreboard_ui_enabled == 2)
401 entity prev_team = NULL;
402 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
404 if(tm.team == NUM_SPECTATOR)
406 if (tm == scoreboard_selected_team)
411 scoreboard_selected_team = prev_team;
415 entity prev_pl = NULL;
417 for(tm = teams.sort_next; tm; tm = tm.sort_next)
419 if(tm.team != NUM_SPECTATOR)
420 for(pl = players.sort_next; pl; pl = pl.sort_next)
422 if(pl.team != tm.team)
424 if (pl == scoreboard_selected_player)
430 scoreboard_selected_player = prev_pl;
434 else if(nPrimary == K_RIGHTARROW)
438 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
439 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
441 else if(nPrimary == K_LEFTARROW)
445 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
446 rankings_start_column = max(rankings_start_column - 1, 0);
448 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
452 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
454 if (scoreboard_ui_enabled == 2)
457 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
460 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
461 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
462 HUD_Scoreboard_UI_Disable();
464 else if (scoreboard_selected_player)
465 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
468 else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
472 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
474 switch (scoreboard_selected_columns_layout)
477 if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
479 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
480 scoreboard_selected_columns_layout = 1;
485 localcmd(sprintf("scoreboard_columns_set default\n"));
486 scoreboard_selected_columns_layout = 2;
489 localcmd(sprintf("scoreboard_columns_set all\n"));
490 scoreboard_selected_columns_layout = 0;
495 else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
499 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
500 localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
502 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
506 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
508 if (scoreboard_selected_player)
510 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
511 HUD_Scoreboard_UI_Disable();
515 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
519 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
521 if (scoreboard_selected_player)
522 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
525 else if(hit_con_bind || nPrimary == K_PAUSE)
531 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
532 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
534 void Scoreboard_InitScores()
538 ps_primary = ps_secondary = NULL;
539 ts_primary = ts_secondary = -1;
540 FOREACH(Scores, true, {
541 if(scores_flags(it) & SFL_NOT_SORTABLE)
543 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
544 if(f == SFL_SORT_PRIO_PRIMARY)
546 if(f == SFL_SORT_PRIO_SECONDARY)
549 if(ps_secondary == NULL)
550 ps_secondary = ps_primary;
552 for(i = 0; i < MAX_TEAMSCORE; ++i)
554 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
555 if(f == SFL_SORT_PRIO_PRIMARY)
557 if(f == SFL_SORT_PRIO_SECONDARY)
560 if(ts_secondary == -1)
561 ts_secondary = ts_primary;
563 Cmd_Scoreboard_SetFields(0);
567 void Scoreboard_UpdatePlayerTeams()
569 static float update_time;
570 if (time <= update_time)
577 for(pl = players.sort_next; pl; pl = pl.sort_next)
579 numplayers += pl.team != NUM_SPECTATOR;
581 int Team = entcs_GetScoreTeam(pl.sv_entnum);
582 if(SetTeam(pl, Team))
585 Scoreboard_UpdatePlayerPos(pl);
589 pl = players.sort_next;
594 print(strcat("PNUM: ", ftos(num), "\n"));
599 int Scoreboard_CompareScore(int vl, int vr, int f)
601 TC(int, vl); TC(int, vr); TC(int, f);
602 if(f & SFL_ZERO_IS_WORST)
604 if(vl == 0 && vr != 0)
606 if(vl != 0 && vr == 0)
610 return IS_INCREASING(f);
612 return IS_DECREASING(f);
616 float Scoreboard_ComparePlayerScores(entity left, entity right)
618 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
619 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
626 if(vl == NUM_SPECTATOR)
628 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
630 if(!left.gotscores && right.gotscores)
635 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
636 if (res >= 0) return res;
638 if (ps_secondary && ps_secondary != ps_primary)
640 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
641 if (res >= 0) return res;
644 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
645 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
646 if (res >= 0) return res;
649 if (left.sv_entnum < right.sv_entnum)
655 void Scoreboard_UpdatePlayerPos(entity player)
658 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
660 SORT_SWAP(player, ent);
662 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
664 SORT_SWAP(ent, player);
668 float Scoreboard_CompareTeamScores(entity left, entity right)
670 if(left.team == NUM_SPECTATOR)
672 if(right.team == NUM_SPECTATOR)
677 for(int i = -2; i < MAX_TEAMSCORE; ++i)
681 if (fld_idx == -1) fld_idx = ts_primary;
682 else if (ts_secondary == ts_primary) continue;
683 else fld_idx = ts_secondary;
688 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
691 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
692 if (r >= 0) return r;
695 if (left.team < right.team)
701 void Scoreboard_UpdateTeamPos(entity Team)
704 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
706 SORT_SWAP(Team, ent);
708 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
710 SORT_SWAP(ent, Team);
714 void Cmd_Scoreboard_Help()
716 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
717 LOG_HELP(_("Usage:"));
718 LOG_HELP("^2scoreboard_columns_set ^3default");
719 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
720 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
721 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
722 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
723 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
724 LOG_HELP(_("The following field names are recognized (case insensitive):"));
730 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
731 "of game types, then a slash, to make the field show up only in these\n"
732 "or in all but these game types. You can also specify 'all' as a\n"
733 "field to show all fields available for the current game mode."));
736 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
737 "include/exclude ALL teams/noteams game modes."));
740 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
741 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
742 "right of the vertical bar aligned to the right."));
743 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
744 "other gamemodes except DM."));
747 // NOTE: adding a gametype with ? to not warn for an optional field
748 // make sure it's excluded in a previous exclusive rule, if any
749 // otherwise the previous exclusive rule warns anyway
750 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
751 #define SCOREBOARD_DEFAULT_COLUMNS \
752 "ping pl fps cn name |" \
753 " -teams,rc,cts,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
754 " -teams,lms/deaths +ft,tdm,tmayhem/deaths" \
756 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
757 " -cts,dm,tdm,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
758 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
759 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
760 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
761 " +lms/lives +lms/rank" \
762 " +kh/kckills +kh/losses +kh/caps" \
763 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
764 " +as/objectives +nb/faults +nb/goals" \
765 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
766 " +dom/ticks +dom/takes" \
767 " -lms,rc,cts,inv,nb/score"
769 void Cmd_Scoreboard_SetFields(int argc)
774 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
778 return; // do nothing, we don't know gametype and scores yet
780 // sbt_fields uses strunzone on the titles!
781 if(!sbt_field_title[0])
782 for(i = 0; i < MAX_SBT_FIELDS; ++i)
783 sbt_field_title[i] = strzone("(null)");
785 // TODO: re enable with gametype dependant cvars?
786 if(argc < 3) // no arguments provided
787 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
790 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
794 if(argv(2) == "default" || argv(2) == "expand_default")
796 if(argv(2) == "expand_default")
797 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
798 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
800 else if(argv(2) == "all" || argv(2) == "ALL")
802 string s = "ping pl cn name |"; // scores without label (not really scores)
805 // scores without label
806 s = strcat(s, " ", "sum");
807 s = strcat(s, " ", "kdratio");
808 s = strcat(s, " ", "frags");
810 FOREACH(Scores, true, {
812 if(it != ps_secondary)
813 if(scores_label(it) != "")
814 s = strcat(s, " ", scores_label(it));
816 if(ps_secondary != ps_primary)
817 s = strcat(s, " ", scores_label(ps_secondary));
818 s = strcat(s, " ", scores_label(ps_primary));
819 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
826 hud_fontsize = HUD_GetFontsize("hud_fontsize");
828 duel_score_fontsize = hud_fontsize * 3;
829 duel_name_fontsize = hud_fontsize * 1.5;
830 duel_score_size = vec2(duel_score_fontsize.x * 1.5, duel_score_fontsize.y * 1.25);
832 team_score_fontsize = hud_fontsize * 2;
833 team_name_fontsize = hud_fontsize * 1.5;
834 team_score_size = vec2(team_score_fontsize.x * 1.5, team_score_fontsize.y * 1.25);
836 for(i = 1; i < argc - 1; ++i)
839 bool nocomplain = false;
840 if(substring(str, 0, 1) == "?")
843 str = substring(str, 1, strlen(str) - 1);
846 slash = strstrofs(str, "/", 0);
849 pattern = substring(str, 0, slash);
850 str = substring(str, slash + 1, strlen(str) - (slash + 1));
852 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
856 str = strtolower(str);
857 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
858 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
863 // fields without a label (not networked via the score system)
864 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
865 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
866 case "cn": sbt_field[sbt_num_fields] = SP_COUNTRY; break; //LegendGuard adds cn label for Country column 05-04-2021
867 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
868 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
869 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
870 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
871 case "cn": sbt_field[sbt_num_fields] = SP_COUNTRY; break; //LegendGuard adds cn label for Country column 05-04-2021
872 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
873 default: // fields with a label
875 // map alternative labels
876 if (str == "damage") str = "dmg";
877 if (str == "damagetaken") str = "dmgtaken";
879 FOREACH(Scores, true, {
880 if (str == strtolower(scores_label(it))) {
882 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
886 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
887 if(!nocomplain && str != "fps") // server can disable the fps field
888 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
890 strfree(sbt_field_title[sbt_num_fields]);
891 sbt_field_size[sbt_num_fields] = 0;
895 sbt_field[sbt_num_fields] = j;
898 if(j == ps_secondary)
899 have_secondary = true;
904 if(sbt_num_fields >= MAX_SBT_FIELDS)
908 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
910 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
911 have_secondary = true;
912 if(ps_primary == ps_secondary)
913 have_secondary = true;
914 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
916 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
920 strfree(sbt_field_title[sbt_num_fields]);
921 for(i = sbt_num_fields; i > 0; --i)
923 sbt_field_title[i] = sbt_field_title[i-1];
924 sbt_field_size[i] = sbt_field_size[i-1];
925 sbt_field[i] = sbt_field[i-1];
927 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
928 sbt_field[0] = SP_NAME;
930 LOG_INFO("fixed missing field 'name'");
934 strfree(sbt_field_title[sbt_num_fields]);
935 for(i = sbt_num_fields; i > 1; --i)
937 sbt_field_title[i] = sbt_field_title[i-1];
938 sbt_field_size[i] = sbt_field_size[i-1];
939 sbt_field[i] = sbt_field[i-1];
941 sbt_field_title[1] = strzone("|");
942 sbt_field[1] = SP_SEPARATOR;
943 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
945 LOG_INFO("fixed missing field '|'");
948 else if(!have_separator)
950 strcpy(sbt_field_title[sbt_num_fields], "|");
951 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
952 sbt_field[sbt_num_fields] = SP_SEPARATOR;
954 LOG_INFO("fixed missing field '|'");
958 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
959 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
960 sbt_field[sbt_num_fields] = ps_secondary;
962 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
966 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
967 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
968 sbt_field[sbt_num_fields] = ps_primary;
970 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
974 sbt_field[sbt_num_fields] = SP_END;
977 string Scoreboard_AddPlayerId(string pl_name, entity pl)
979 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
980 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
981 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
985 vector sbt_field_rgb;
986 string sbt_field_icon0;
987 string sbt_field_icon1;
988 string sbt_field_icon2;
989 string sbt_field_icon3; //LegendGuard adds for Country player flags 05-04-2021
990 vector sbt_field_icon0_rgb;
991 vector sbt_field_icon1_rgb;
992 vector sbt_field_icon2_rgb;
993 string Scoreboard_GetName(entity pl)
995 if(ready_waiting && pl.ready)
997 sbt_field_icon0 = "gfx/scoreboard/player_ready";
1001 int f = entcs_GetClientColors(pl.sv_entnum);
1003 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
1004 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
1005 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
1006 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
1007 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
1010 if(entcs_GetRank(pl.sv_entnum) != "")
1011 return strcat(entcs_GetRank(pl.sv_entnum), "^7 ", entcs_GetName(pl.sv_entnum));
1013 return entcs_GetName(pl.sv_entnum);
1016 //LegendGuard adds GetCountrycode function 05-04-2021
1017 string Scoreboard_GetCountrycode(entity pl)
1019 int ccode = entcs_GetCountryCode(pl.sv_entnum);
1021 sbt_field_icon3 = strcat("gfx/flags/", ftos(ccode));
1023 sbt_field_icon3 = strcat("gfx/flags/", ftos(0)); //if user hasn't assigned country flag
1028 int autocvar_hud_panel_scoreboard_ping_best = 0;
1029 int autocvar_hud_panel_scoreboard_ping_medium = 70;
1030 int autocvar_hud_panel_scoreboard_ping_high = 100;
1031 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1032 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1033 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1034 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1035 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1036 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1037 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1038 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1039 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1040 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1041 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1042 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1043 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1045 vector getPingColor(float f)
1049 else if(f < PING_MED)
1050 return COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1051 else if(f < PING_HIGH)
1052 return COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1053 else if(f < PING_WORST)
1054 return COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1059 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1061 float tmp, num, denom;
1064 sbt_field_rgb = '1 1 1';
1065 sbt_field_icon0 = "";
1066 sbt_field_icon1 = "";
1067 sbt_field_icon2 = "";
1068 sbt_field_icon3 = ""; //LegendGuard adds for Country column 05-04-2021
1069 sbt_field_icon0_rgb = '1 1 1';
1070 sbt_field_icon1_rgb = '1 1 1';
1071 sbt_field_icon2_rgb = '1 1 1';
1072 int rounds_played = 0;
1074 rounds_played = pl.(scores(SP_ROUNDS_PL));
1079 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1080 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1084 sbt_field_rgb = getPingColor(f);
1090 f = pl.ping_packetloss;
1091 tmp = pl.ping_movementloss;
1092 if(f == 0 && tmp == 0)
1094 str = ftos(ceil(f * 100));
1096 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1097 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1098 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1101 //LegendGuard adds Country REGISTER in the switch 05-04-2021
1103 str = Scoreboard_GetCountrycode(pl);
1106 //LegendGuard adds Country REGISTER in the switch 05-04-2021
1108 str = Scoreboard_GetCountrycode(pl);
1112 str = Scoreboard_GetName(pl);
1113 if (autocvar_hud_panel_scoreboard_playerid)
1114 str = Scoreboard_AddPlayerId(str, pl);
1118 f = pl.(scores(SP_KILLS));
1119 f -= pl.(scores(SP_SUICIDES));
1121 return sprintf("%.1f", f / rounds_played);
1125 num = pl.(scores(SP_KILLS));
1126 denom = pl.(scores(SP_DEATHS));
1129 sbt_field_rgb = '0 1 0';
1131 str = sprintf("%.1f", num / rounds_played);
1133 str = sprintf("%d", num);
1134 } else if(num <= 0) {
1135 sbt_field_rgb = '1 0 0';
1137 str = sprintf("%.2f", num / (denom * rounds_played));
1139 str = sprintf("%.1f", num / denom);
1143 str = sprintf("%.2f", num / (denom * rounds_played));
1145 str = sprintf("%.1f", num / denom);
1150 f = pl.(scores(SP_KILLS));
1151 f -= pl.(scores(SP_DEATHS));
1154 sbt_field_rgb = '0 1 0';
1156 sbt_field_rgb = '1 1 1';
1158 sbt_field_rgb = '1 0 0';
1161 return sprintf("%.1f", f / rounds_played);
1166 float elo = pl.(scores(SP_ELO));
1168 case -1: return "...";
1169 case -2: return _("N/A");
1170 default: return ftos(elo);
1176 float fps = pl.(scores(SP_FPS));
1179 sbt_field_rgb = '1 1 1';
1180 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1183 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1184 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1189 return ftos(pl.(scores(field)));
1191 case SP_DMG: case SP_DMGTAKEN:
1193 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1194 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1196 default: case SP_SCORE:
1197 tmp = pl.(scores(field));
1198 f = scores_flags(field);
1199 if(field == ps_primary)
1200 sbt_field_rgb = '1 1 0';
1201 else if(field == ps_secondary)
1202 sbt_field_rgb = '0 1 1';
1204 sbt_field_rgb = '1 1 1';
1205 return ScoreString(f, tmp, rounds_played);
1210 float sbt_fixcolumnwidth_len;
1211 float sbt_fixcolumnwidth_iconlen;
1212 float sbt_fixcolumnwidth_marginlen;
1214 string Scoreboard_FixColumnWidth(int i, string str)
1220 sbt_fixcolumnwidth_iconlen = 0;
1222 if(sbt_field_icon0 != "")
1224 sz = draw_getimagesize(sbt_field_icon0);
1226 if(sbt_fixcolumnwidth_iconlen < f)
1227 sbt_fixcolumnwidth_iconlen = f;
1230 if(sbt_field_icon1 != "")
1232 sz = draw_getimagesize(sbt_field_icon1);
1234 if(sbt_fixcolumnwidth_iconlen < f)
1235 sbt_fixcolumnwidth_iconlen = f;
1238 if(sbt_field_icon2 != "")
1240 sz = draw_getimagesize(sbt_field_icon2);
1242 if(sbt_fixcolumnwidth_iconlen < f)
1243 sbt_fixcolumnwidth_iconlen = f;
1246 //LegendGuard adds conditional for Country column 05-04-2021
1247 if(sbt_field_icon3 != "")
1249 sz = draw_getimagesize(sbt_field_icon3);
1251 if(sbt_fixcolumnwidth_iconlen < f)
1252 sbt_fixcolumnwidth_iconlen = f;
1255 if(sbt_fixcolumnwidth_iconlen != 0)
1257 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1258 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1261 sbt_fixcolumnwidth_marginlen = 0;
1263 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1266 float remaining_space = 0;
1267 for(j = 0; j < sbt_num_fields; ++j)
1269 if (sbt_field[i] != SP_SEPARATOR)
1270 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1271 sbt_field_size[i] = panel_size.x - remaining_space;
1273 if (sbt_fixcolumnwidth_iconlen != 0)
1274 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1275 float namesize = panel_size.x - remaining_space;
1276 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1277 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1279 max_namesize = vid_conwidth - remaining_space;
1282 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1284 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1285 if(sbt_field_size[i] < f)
1286 sbt_field_size[i] = f;
1291 void Scoreboard_initFieldSizes()
1293 for(int i = 0; i < sbt_num_fields; ++i)
1295 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1296 Scoreboard_FixColumnWidth(i, "");
1300 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players, int team)
1305 vector column_dim = eY * panel_size.y;
1308 column_dim.y -= 1.25 * hud_fontsize.y;
1309 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1310 pos.x += hud_fontsize.x * 0.5;
1311 for(i = 0; i < sbt_num_fields; ++i)
1313 if(sbt_field[i] == SP_SEPARATOR)
1316 vector text_offset_center = '0 0 0';
1318 if(sbt_field[i] == SP_PING && teamplay) {
1319 title_str = sprintf("(%d)", average_ping[Team_TeamToIndex(team) - 1]);
1320 title_rgb = getPingColor(average_ping[Team_TeamToIndex(team) - 1]);
1321 text_offset_center.x = sbt_field_size[i] - stringwidth(title_str, false, hud_fontsize);
1323 title_str = sbt_field_title[i];
1324 title_rgb = rgb * 1.5;
1327 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1330 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1331 drawstring(pos + text_offset + text_offset_center, title_str, hud_fontsize, title_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1332 pos.x += column_dim.x;
1334 if(sbt_field[i] == SP_SEPARATOR)
1336 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1337 for(i = sbt_num_fields - 1; i > 0; --i)
1339 if(sbt_field[i] == SP_SEPARATOR)
1342 pos.x -= sbt_field_size[i];
1347 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1348 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1351 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1352 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1353 pos.x -= hud_fontsize.x;
1357 pos.x = panel_pos.x;
1358 pos.y += 1.25 * hud_fontsize.y;
1362 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1364 TC(bool, is_self); TC(int, pl_number);
1366 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1368 vector h_pos = item_pos;
1369 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1370 // alternated rows highlighting
1371 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1373 if (pl == scoreboard_selected_player)
1374 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1377 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1378 else if((sbt_highlight) && (!(pl_number % 2)))
1379 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1381 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1383 vector pos = item_pos;
1384 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1386 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1388 pos.x += hud_fontsize.x * 0.5;
1389 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1390 vector tmp = '0 0 0';
1392 PlayerScoreField field;
1393 for(i = 0; i < sbt_num_fields; ++i)
1395 field = sbt_field[i];
1396 if(field == SP_SEPARATOR)
1399 if(is_spec && field != SP_NAME && field != SP_PING) {
1400 pos.x += sbt_field_size[i] + hud_fontsize.x;
1403 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1404 str = Scoreboard_FixColumnWidth(i, str);
1406 pos.x += sbt_field_size[i] + hud_fontsize.x;
1408 if(field == SP_NAME) {
1409 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1410 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1412 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1413 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1416 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1417 if(sbt_field_icon0 != "")
1418 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1419 if(sbt_field_icon1 != "")
1420 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1421 if(sbt_field_icon2 != "")
1422 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1423 if(sbt_field_icon3 != "") //LegendGuard adds conditional for Country column 05-04-2021
1424 drawpic(pos - tmp, sbt_field_icon3, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1427 if(sbt_field[i] == SP_SEPARATOR)
1429 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1430 for(i = sbt_num_fields-1; i > 0; --i)
1432 field = sbt_field[i];
1433 if(field == SP_SEPARATOR)
1436 if(is_spec && field != SP_NAME && field != SP_PING) {
1437 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1441 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1442 str = Scoreboard_FixColumnWidth(i, str);
1444 if(field == SP_NAME) {
1445 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1446 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1448 tmp.x = sbt_fixcolumnwidth_len;
1449 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1452 tmp.x = sbt_field_size[i];
1453 if(sbt_field_icon0 != "")
1454 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1455 if(sbt_field_icon1 != "")
1456 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1457 if(sbt_field_icon2 != "")
1458 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1459 if(sbt_field_icon3 != "") //LegendGuard adds conditional for Country column 05-04-2021
1460 drawpic(pos - tmp, sbt_field_icon3, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1461 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1466 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1469 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1472 vector h_pos = item_pos;
1473 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1476 bool complete = (this_team == NUM_SPECTATOR);
1479 if((sbt_highlight) && (!(pl_number % 2)))
1480 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1482 vector pos = item_pos;
1483 pos.x += hud_fontsize.x * 0.5;
1484 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1486 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1488 width_limit -= stringwidth("...", false, hud_fontsize);
1489 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1490 static float max_name_width = 0;
1492 float fieldsize = 0;
1493 float min_fieldsize = 0;
1494 float fieldpadding = hud_fontsize.x * 0.25;
1495 if(this_team == NUM_SPECTATOR)
1497 if(autocvar_hud_panel_scoreboard_spectators_showping)
1498 min_fieldsize = stringwidth("999", false, hud_fontsize);
1500 else if(autocvar_hud_panel_scoreboard_others_showscore)
1501 min_fieldsize = stringwidth("99", false, hud_fontsize);
1502 for(i = 0; pl; pl = pl.sort_next)
1504 if(pl.team != this_team)
1506 if(pl == ignored_pl)
1509 string flag_name = "";
1510 vector flag_size = '0 0 0';
1511 Scoreboard_GetField(pl, SP_COUNTRY, autocvar_hud_panel_scoreboard_scores_per_round);
1513 if(sbt_field_icon3 != "") {
1514 sz = draw_getimagesize(sbt_field_icon3);
1515 flag_name = sbt_field_icon3;
1516 flag_size = vec2(hud_fontsize.x * (sz.x / sz.y), hud_fontsize.y);
1519 if(entcs_GetWantsJoin(pl.sv_entnum))
1521 vector tmcolor = Team_ColorRGB(Team_IndexToTeam(entcs_GetWantsJoin(pl.sv_entnum)));
1522 tmcolor -= tmcolor * sin(2*M_PI*time);
1524 drawstring(pos, "(Q)", hud_fontsize, tmcolor, sbt_fg_alpha, DRAWFLAG_NORMAL);
1525 pos.x += stringwidth("(Q) ", true, hud_fontsize);
1529 if(this_team == NUM_SPECTATOR)
1531 if(autocvar_hud_panel_scoreboard_spectators_showping)
1532 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1534 else if(autocvar_hud_panel_scoreboard_others_showscore)
1535 field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1538 if(entcs_GetRank(pl.sv_entnum) != "")
1539 str = strcat(entcs_GetRank(pl.sv_entnum), "^7 ", entcs_GetName(pl.sv_entnum));
1541 str = entcs_GetName(pl.sv_entnum);
1542 if (autocvar_hud_panel_scoreboard_playerid)
1543 str = Scoreboard_AddPlayerId(str, pl);
1544 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1545 float column_width = stringwidth(str, true, hud_fontsize);
1546 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1548 if(column_width > max_name_width)
1549 max_name_width = column_width;
1550 column_width = max_name_width;
1554 fieldsize = stringwidth(field, false, hud_fontsize);
1555 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1558 if(pos.x + column_width > width_limit)
1563 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1568 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1569 pos.y += hud_fontsize.y * 1.25;
1573 if(flag_name != "") {
1574 drawpic(pos, flag_name, flag_size, sbt_field_icon1_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1575 pos.x += flag_size.x + hud_fontsize.x * 0.5;
1578 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1580 if (pl == scoreboard_selected_player)
1582 h_size.x = column_width + hud_fontsize.x * 0.25;
1583 h_size.y = hud_fontsize.y;
1584 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1588 vector name_pos = pos;
1589 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1590 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1592 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1595 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1596 h_size.y = hud_fontsize.y;
1597 vector field_pos = pos;
1598 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1599 field_pos.x += column_width - h_size.x;
1601 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1602 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1603 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1607 h_size.x = column_width + hud_fontsize.x * 0.25;
1608 h_size.y = hud_fontsize.y;
1609 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1611 pos.x += column_width;
1612 pos.x += hud_fontsize.x;
1614 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1617 vector Scoreboard_DrawMedal(vector pos, string icon, float height, float number)
1619 if(!number) return pos;
1620 total_medals += number;
1622 vector tmp_sz, tmp_sz2;
1623 tmp_sz = draw_getimagesize(icon);
1624 tmp_sz2 = vec2(height*(tmp_sz.x/tmp_sz.y), height);
1625 string val = ftos(number);
1627 drawpic(pos, icon, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1629 pos.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1630 drawstring(pos + eY * ((tmp_sz2.y - hud_fontsize.y) / 2), val, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1632 pos.x += stringwidth(val, false, hud_fontsize) + hud_fontsize.x * 0.5;
1636 vector Scoreboard_Duel_DrawPickup(vector pos, bool skinned, string icon, vector sz, float number, bool invert)
1638 vector tmp_in = pos;
1639 vector tmp_sz, tmp_sz2;
1644 picpath = strcat(hud_skin_path, "/", icon);
1645 if(precache_pic(picpath) == "")
1646 picpath = strcat("gfx/hud/default/", icon);
1651 tmp_sz = draw_getimagesize(picpath);
1652 tmp_sz2 = vec2(sz.y*(tmp_sz.x/tmp_sz.y), sz.y);
1654 tmp_in.x = pos.x + ((sz.x - tmp_sz2.x) / 2);
1655 drawpic(tmp_in, picpath, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1659 tmp_in.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1661 tmp_in.x -= hud_fontsize.x * 0.25 + hud_fontsize.x;
1663 tmp_in.y += (tmp_sz2.y - hud_fontsize.y) / 2;
1665 ((number == -1) ? "?" : ftos(number)),
1666 hud_fontsize, ((number > 0) ? '1 1 1' : '0.5 0.5 0.5'),
1670 pos.y += sz.y * 1.1;
1674 int left_pl_dmg = 50;
1675 int right_pl_dmg = 50;
1676 void Scoreboard_Duel_DrawTable(vector pos, bool invert, entity pl, entity tm)
1678 vector tmp, tmp_in, tmp_sz, tmp_acc;
1681 float average_acc = 0;
1687 // Stop here if there are no scores available
1689 if(entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE) return;
1692 tmp.x += panel_bg_padding;
1693 tmp.y += panel_bg_padding;
1694 panel_size.x -= panel_bg_padding * 2;
1697 // drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", tmp, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1700 if(invert) { tmp.x += panel_size.x; tmp.x -= duel_score_size.x; }
1701 drawfill(tmp, duel_score_size, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1704 tmp_str = ftos(pl.(scores(SP_SCORE)));
1706 tmp_in.x += (duel_score_size.x / 2) - (stringwidth(tmp_str, true, duel_score_fontsize) / 2);
1707 tmp_in.y += (duel_score_size.y / 2) - (duel_score_fontsize.y / 2);
1709 draw_beginBoldFont();
1710 drawstring(tmp_in, tmp_str, duel_score_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1714 tmp_in.y += (duel_score_size.y - duel_name_fontsize.y) / 2;
1717 string rank_str = entcs_GetRank(pl.sv_entnum);
1718 if(rank_str != "") {
1720 tmp_in.x -= stringwidth_colors(rank_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1722 tmp_in.x += duel_score_size.x + duel_name_fontsize.x * 0.5;
1724 draw_beginBoldFont();
1725 drawcolorcodedstring(tmp_in, rank_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1730 tmp_str = entcs_GetName(pl.sv_entnum);
1732 tmp_in.x -= stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1734 tmp_in.x += (rank_str != "" ? stringwidth_colors(rank_str, duel_name_fontsize) : duel_score_size.x) + duel_name_fontsize.x * 0.5;
1735 drawcolorcodedstring(tmp_in, tmp_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1737 //LegendGuard adds a conditional sentence for country column 05-04-2021
1738 // Player country icon/flag
1739 Scoreboard_GetField(pl, SP_COUNTRY, autocvar_hud_panel_scoreboard_scores_per_round);
1740 if(sbt_field_icon3 != "") {
1741 vector rsz = draw_getimagesize(sbt_field_icon3);
1742 sbt_fixcolumnwidth_iconlen = rsz.x / rsz.y;
1744 tmp_in.x -= hud_fontsize.x * sbt_fixcolumnwidth_iconlen + duel_name_fontsize.x * 0.5;
1746 tmp_in.x += stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1747 tmp_in.y += (duel_name_fontsize.y - hud_fontsize.y) / 2;
1748 drawpic(tmp_in, sbt_field_icon3, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1752 float column_width = panel_size.x / 5;
1753 tmp.x = pos.x + panel_bg_padding;
1754 tmp.y += hud_fontsize.y * 3 + hud_fontsize.y;
1759 i = (invert ? 4 : 0);
1760 column_dim = vec2(column_width * 4, hud_fontsize.y);
1762 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("kills", false, hud_fontsize) / 2),
1763 "kills", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1764 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("dmg", false, hud_fontsize) / 2),
1765 "dmg", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1766 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("acc", false, hud_fontsize) / 2),
1767 "acc", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1768 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("hits", false, hud_fontsize) / 2),
1769 "hits", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1770 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("ping", false, hud_fontsize) / 2),
1771 "ping", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1773 tmp.x = pos.x + panel_bg_padding;
1774 tmp.y += hud_fontsize.y;
1777 i = (invert ? 4 : 0);
1779 tmp_str = ftos(pl.(scores(SP_KILLS)));
1780 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1781 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1783 tmp_str = ftos(pl.(scores(SP_DMG)));
1784 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1785 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1787 tmp_acc = tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2);
1794 tmp_str = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1795 drawstring(tmp + eX * column_width * i + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1796 tmp_str, hud_fontsize * 1.25, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1798 tmp_str = Scoreboard_GetField(pl, SP_PL, autocvar_hud_panel_scoreboard_scores_per_round);
1799 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 0.75) / 2) + eY * (hud_fontsize.y * 1.25),
1800 tmp_str, hud_fontsize * 0.75, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1802 tmp.x = pos.x + panel_bg_padding;
1803 tmp.y += hud_fontsize.y * 2;
1812 int used_weapons = 0;
1814 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1815 FOREACH(Weapons, it != WEP_Null, {
1816 WepSet set = it.m_wepset;
1817 if (!(weapons_inmap & set) && it != WEP_BLASTER && it != WEP_SHOTGUN) // z411 TODO : We'll be hardcoding this for now.
1819 if (it.spawnflags & WEP_TYPE_OTHER)
1822 int weapon_cnt_fired = pl.accuracy_cnt_fired[i - WEP_FIRST];
1823 int weapon_cnt_hit = pl.accuracy_cnt_hit[i - WEP_FIRST];
1825 if(weapon_cnt_fired)
1826 weapon_acc = floor((weapon_cnt_hit / weapon_cnt_fired) * 100);
1827 average_acc += weapon_acc;
1829 // center vertically
1830 vector row_in = tmp_in;
1831 row_in.y += ((hud_fontsize.y * autocvar_hud_panel_scoreboard_duel_weapon_scale) - column_dim.y) / 2;
1833 // draw row background
1834 drawfill(row_in + eX * column_width * (invert ? 1 : 0), column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1836 if(weapon_cnt_fired) {
1838 if(pl.accuracy_hit[i - WEP_FIRST] > left_pl_dmg)
1839 left_pl_dmg = pl.accuracy_hit[i - WEP_FIRST];
1840 dmg_percent = pl.accuracy_hit[i - WEP_FIRST] / left_pl_dmg;
1842 if(pl.accuracy_hit[i - WEP_FIRST] > right_pl_dmg)
1843 right_pl_dmg = pl.accuracy_hit[i - WEP_FIRST];
1844 dmg_percent = pl.accuracy_hit[i - WEP_FIRST] / right_pl_dmg;
1847 // convert percentage range to 0.4 - 1
1848 dmg_percent = dmg_percent * (1 - 0.4) + 0.4;
1850 dmg_color.x = dmg_percent;
1851 dmg_color.y = dmg_percent;
1852 dmg_color.z = dmg_percent;
1857 int c = (invert ? 4 : 0);
1859 draw_str = ftos(pl.accuracy_frags[i - WEP_FIRST]);
1860 drawstring(row_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1861 draw_str, hud_fontsize, dmg_color, panel_fg_alpha, DRAWFLAG_NORMAL);
1863 draw_str = ftos(pl.accuracy_hit[i - WEP_FIRST]);
1864 drawstring(row_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1865 draw_str, hud_fontsize, dmg_color, panel_fg_alpha, DRAWFLAG_NORMAL);
1867 draw_str = sprintf("%d%%", weapon_acc);
1868 drawstring(row_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1869 draw_str, hud_fontsize, dmg_color, panel_fg_alpha, DRAWFLAG_NORMAL);
1871 draw_str = strcat(ftos(weapon_cnt_hit), " / ", ftos(weapon_cnt_fired));
1872 drawstring(row_in + eX * column_width * (invert ? c-- : c++) + eX * (column_width / 2) - eX * stringwidth(ftos(weapon_cnt_hit), false, hud_fontsize) - eX * hud_fontsize.x * 0.5,
1873 draw_str, hud_fontsize, dmg_color, panel_fg_alpha, DRAWFLAG_NORMAL);
1880 tmp_in.x = pos.x + panel_size.x - panel_bg_padding - hud_fontsize.x / 2;
1881 drawpic_aspect_skin(tmp_in, it.model2, vec2(50, hud_fontsize.y * autocvar_hud_panel_scoreboard_duel_weapon_scale), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1884 tmp_in.x = pos.x + panel_bg_padding;
1885 tmp_in.y += hud_fontsize.y * autocvar_hud_panel_scoreboard_duel_weapon_scale;
1891 average_acc = floor((average_acc / used_weapons) + 0.5);
1893 // draw total accuracy now
1894 tmp_str = sprintf("%d%%", average_acc);
1895 drawstring(tmp_acc - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1896 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1899 vector icon_sz = vec2(column_width, hud_fontsize.y*1.5);
1902 tmp.x += column_width * 4;
1904 drawstring(tmp + eX * ((column_width - stringwidth("medals", false, hud_fontsize)) / 2),
1905 "medals", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1906 tmp.y += hud_fontsize.y * 1.25;
1908 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/humiliation", icon_sz, pl.(scores(SP_MEDAL_HUMILIATION)), invert);
1909 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/impressive", icon_sz, pl.(scores(SP_MEDAL_IMPRESSIVE)), invert);
1910 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/excellent", icon_sz, pl.(scores(SP_MEDAL_EXCELLENT)), invert);
1913 drawstring(tmp + eX * ((column_width - stringwidth("items", false, hud_fontsize)) / 2),
1914 "items", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1915 tmp.y += hud_fontsize.y * 1.25;
1919 it.m_id == ITEM_ArmorMega.m_id ||
1920 it.m_id == ITEM_HealthMega.m_id ||
1921 it.m_id == ITEM_ArmorBig.m_id, {
1922 // If the match isn't over, Only show pickups if we're spectating or they're our own
1923 if(intermission || warmup_stage || spectatee_status || pl.sv_entnum == current_player)
1924 inv_num = inventoryslots[pl.sv_entnum].inv_items[it.m_id];
1925 tmp = Scoreboard_Duel_DrawPickup(tmp, true, it.m_icon, icon_sz, inv_num, invert);
1927 if(it.m_id == REGISTRY_MAX(Items))
1931 vector Scoreboard_MakeDuelTable(vector pos, entity tm, vector rgb, vector bg_size)
1933 vector end_pos = pos;
1934 float screen_half = panel_size.x / 2;
1935 float weapon_margin = hud_fontsize.x;
1937 panel_size.x = screen_half - weapon_margin;
1939 panel_size.y = max(duel_score_size.y * 5.5, duel_score_size.y * 2.25 + (hud_fontsize.y * autocvar_hud_panel_scoreboard_duel_weapon_scale * total_weapons));
1941 panel_size.y = duel_score_size.y * 5.5;
1943 entity pl_left = players.sort_next;
1944 entity pl_right = pl_left.sort_next;
1946 Scoreboard_Duel_DrawTable(pos, true, pl_left, tm);
1947 Scoreboard_Duel_DrawTable(pos + eX * screen_half + eX * weapon_margin, false, pl_right, tm);
1949 end_pos.y += panel_size.y + (panel_bg_padding * 2);
1950 panel_size.x = screen_half * 2;
1954 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1956 int max_players = 999;
1957 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1959 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1962 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1963 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1964 height /= team_count;
1967 height -= panel_bg_padding * 2; // - padding
1968 max_players = floor(height / (hud_fontsize.y * 1.25));
1969 if(max_players <= 1)
1971 if(max_players == tm.team_size)
1976 entity me = playerslots[current_player];
1978 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1979 panel_size.y += panel_bg_padding * 2;
1981 vector scoreboard_selected_hl_pos = pos;
1982 vector scoreboard_selected_hl_size = '0 0 0';
1983 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1984 scoreboard_selected_hl_size.y = panel_size.y;
1988 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1989 if(panel.current_panel_bg != "0")
1990 end_pos.y += panel_bg_border * 2;
1992 if(panel_bg_padding)
1994 panel_pos += '1 1 0' * panel_bg_padding;
1995 panel_size -= '2 2 0' * panel_bg_padding;
1999 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
2003 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
2005 pos.y += 1.25 * hud_fontsize.y;
2008 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
2010 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2013 // print header row and highlight columns
2014 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size), tm.team);
2016 // fill the table and draw the rows
2017 bool is_self = false;
2018 bool self_shown = false;
2021 if(Team_IsValidTeam(tm.team)) average_ping[Team_TeamToIndex(tm.team) - 1] = 0;
2022 for(pl = players.sort_next; pl; pl = pl.sort_next)
2024 if(pl.team != tm.team)
2026 if(i == max_players - 2 && pl != me)
2028 if(!self_shown && me.team == tm.team)
2030 Scoreboard_DrawItem(pos, rgb, me, true, i);
2032 pos.y += 1.25 * hud_fontsize.y;
2036 if(i >= max_players - 1)
2038 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
2041 is_self = (pl.sv_entnum == current_player);
2042 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
2044 if(Team_IsValidTeam(tm.team) && pl.ping) {
2045 average_ping[Team_TeamToIndex(tm.team) - 1] += pl.ping;
2050 pos.y += 1.25 * hud_fontsize.y;
2053 if(with_ping) average_ping[Team_TeamToIndex(tm.team) - 1] /= with_ping;
2055 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
2057 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
2059 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2060 _alpha *= panel_fg_alpha;
2062 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
2066 panel_size.x += panel_bg_padding * 2; // restore initial width
2070 bool Scoreboard_WouldDraw()
2072 if (scoreboard_ui_enabled)
2074 if (scoreboard_ui_disabling)
2076 if (scoreboard_fade_alpha == 0)
2077 HUD_Scoreboard_UI_Disable_Instantly();
2080 if (intermission && scoreboard_ui_enabled == 2)
2082 HUD_Scoreboard_UI_Disable_Instantly();
2087 else if (MUTATOR_CALLHOOK(DrawScoreboard))
2089 else if (QuickMenu_IsOpened())
2091 else if (HUD_Radar_Clickable())
2093 else if (sb_showscores) // set by +showscores engine command
2095 else if (intermission == 1)
2097 else if (intermission == 2)
2099 else if (!spectatee_status && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
2100 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
2104 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
2109 vector Scoreboard_MedalStats_Draw(vector pos)
2112 float height = hud_fontsize.y * 2;
2114 entity pl = playerslots[current_player];
2116 vector title_pos = pos;
2117 pos.x += 0.5 * hud_fontsize.x + panel_bg_padding;
2118 pos.y += 1.25 * hud_fontsize.y;
2122 pos = Scoreboard_DrawMedal(pos, "gfx/medal/airshot", height, pl.(scores(SP_MEDAL_AIRSHOT)));
2123 pos = Scoreboard_DrawMedal(pos, "gfx/medal/damage", height, pl.(scores(SP_MEDAL_DAMAGE)));
2124 pos = Scoreboard_DrawMedal(pos, "gfx/medal/electrobitch", height, pl.(scores(SP_MEDAL_ELECTROBITCH)));
2125 pos = Scoreboard_DrawMedal(pos, "gfx/medal/excellent", height, pl.(scores(SP_MEDAL_EXCELLENT)));
2126 pos = Scoreboard_DrawMedal(pos, "gfx/medal/firstblood", height, pl.(scores(SP_MEDAL_FIRSTBLOOD)));
2127 pos = Scoreboard_DrawMedal(pos, "gfx/medal/headshot", height, pl.(scores(SP_MEDAL_HEADSHOT)));
2128 pos = Scoreboard_DrawMedal(pos, "gfx/medal/humiliation", height, pl.(scores(SP_MEDAL_HUMILIATION)));
2129 pos = Scoreboard_DrawMedal(pos, "gfx/medal/impressive", height, pl.(scores(SP_MEDAL_IMPRESSIVE)));
2130 pos = Scoreboard_DrawMedal(pos, "gfx/medal/yoda", height, pl.(scores(SP_MEDAL_YODA)));
2131 pos = Scoreboard_DrawMedal(pos, "gfx/medal/telefrag", height, pl.(scores(SP_MEDAL_TELEFRAG)));
2134 pos.x += hud_fontsize.x;
2136 pos = Scoreboard_DrawMedal(pos, "gfx/medal/accuracy", height, pl.(scores(SP_MEDAL_ACCURACY)));
2137 pos = Scoreboard_DrawMedal(pos, "gfx/medal/assist", height, pl.(scores(SP_MEDAL_ASSIST)));
2138 pos = Scoreboard_DrawMedal(pos, "gfx/medal/capture", height, pl.(scores(SP_MEDAL_CAPTURE)));
2139 pos = Scoreboard_DrawMedal(pos, "gfx/medal/defense", height, pl.(scores(SP_MEDAL_DEFENSE)));
2140 pos = Scoreboard_DrawMedal(pos, "gfx/medal/perfect", height, pl.(scores(SP_MEDAL_PERFECT)));
2142 if(!total_medals) return orig;
2144 drawstring(title_pos, sprintf(_("Medal stats (total %d)"), total_medals),
2145 hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2148 pos.y += height + hud_fontsize.y * 0.5;
2152 float average_accuracy;
2153 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
2155 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
2157 WepSet weapons_stat = WepSet_GetFromStat();
2158 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
2159 int disownedcnt = 0;
2161 FOREACH(Weapons, it != WEP_Null, {
2162 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2164 WepSet set = it.m_wepset;
2165 if(it.spawnflags & WEP_TYPE_OTHER)
2170 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
2172 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
2179 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
2180 if (weapon_cnt <= 0) return pos;
2183 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
2185 int columns = ceil(weapon_cnt / rows);
2187 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
2188 float weapon_height = hud_fontsize.y * 2.3 / aspect;
2189 float height = weapon_height + hud_fontsize.y;
2191 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);
2192 pos.y += 1.25 * hud_fontsize.y;
2193 if(panel.current_panel_bg != "0")
2194 pos.y += panel_bg_border;
2197 panel_size.y = height * rows;
2198 panel_size.y += panel_bg_padding * 2;
2200 float panel_bg_alpha_save = panel_bg_alpha;
2201 panel_bg_alpha *= scoreboard_acc_fade_alpha;
2203 panel_bg_alpha = panel_bg_alpha_save;
2205 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2206 if(panel.current_panel_bg != "0")
2207 end_pos.y += panel_bg_border * 2;
2209 if(panel_bg_padding)
2211 panel_pos += '1 1 0' * panel_bg_padding;
2212 panel_size -= '2 2 0' * panel_bg_padding;
2216 vector tmp = panel_size;
2218 float weapon_width = tmp.x / columns / rows;
2221 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
2225 // column highlighting
2226 for (int i = 0; i < columns; ++i)
2228 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);
2231 for (int i = 0; i < rows; ++i)
2232 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
2235 average_accuracy = 0;
2236 int weapons_with_stats = 0;
2238 pos.x += weapon_width / 2;
2240 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
2243 Accuracy_LoadColors();
2245 float oldposx = pos.x;
2249 FOREACH(Weapons, it != WEP_Null, {
2250 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2252 WepSet set = it.m_wepset;
2253 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
2255 if (it.spawnflags & WEP_TYPE_OTHER)
2259 if (weapon_stats >= 0)
2260 weapon_alpha = sbt_fg_alpha;
2262 weapon_alpha = 0.2 * sbt_fg_alpha;
2265 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
2267 if (weapon_stats >= 0) {
2268 weapons_with_stats += 1;
2269 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
2271 string s = sprintf("%d%%", weapon_stats * 100);
2272 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
2274 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
2275 rgb = Accuracy_GetColor(weapon_stats);
2277 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
2279 tmpos.x += weapon_width * rows;
2280 pos.x += weapon_width * rows;
2281 if (rows == 2 && column == columns - 1) {
2289 if (weapons_with_stats)
2290 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
2292 panel_size.x += panel_bg_padding * 2; // restore initial width
2297 bool is_item_filtered(entity it)
2299 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
2301 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
2304 if (it.instanceOfArmor || it.instanceOfHealth)
2306 int ha_mask = floor(mask) % 10;
2309 default: return false;
2310 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
2311 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
2312 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
2313 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
2316 if (it.instanceOfAmmo)
2318 int ammo_mask = floor(mask / 10) % 10;
2319 return (ammo_mask == 1);
2324 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
2326 Inventory g_inventory = inventoryslots[current_player];
2327 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
2329 int disowned_cnt = 0;
2330 int uninteresting_cnt = 0;
2331 IL_EACH(default_order_items, true, {
2332 int q = g_inventory.inv_items[it.m_id];
2333 //q = 1; // debug: display all items
2334 if (is_item_filtered(it))
2335 ++uninteresting_cnt;
2339 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
2340 int n = items_cnt - disowned_cnt;
2341 if (n <= 0) return pos;
2343 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
2344 int columns = max(6, ceil(n / rows));
2346 float item_height = hud_fontsize.y * 2.3;
2347 float height = item_height + hud_fontsize.y;
2349 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
2350 pos.y += 1.25 * hud_fontsize.y;
2351 if(panel.current_panel_bg != "0")
2352 pos.y += panel_bg_border;
2355 panel_size.y = height * rows;
2356 panel_size.y += panel_bg_padding * 2;
2358 float panel_bg_alpha_save = panel_bg_alpha;
2359 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
2361 panel_bg_alpha = panel_bg_alpha_save;
2363 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2364 if(panel.current_panel_bg != "0")
2365 end_pos.y += panel_bg_border * 2;
2367 if(panel_bg_padding)
2369 panel_pos += '1 1 0' * panel_bg_padding;
2370 panel_size -= '2 2 0' * panel_bg_padding;
2374 vector tmp = panel_size;
2376 float item_width = tmp.x / columns / rows;
2379 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
2383 // column highlighting
2384 for (int i = 0; i < columns; ++i)
2386 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);
2389 for (int i = 0; i < rows; ++i)
2390 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
2394 pos.x += item_width / 2;
2396 float oldposx = pos.x;
2400 IL_EACH(default_order_items, !is_item_filtered(it), {
2401 int n = g_inventory.inv_items[it.m_id];
2402 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
2403 if (n <= 0) continue;
2404 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);
2406 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
2407 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
2408 tmpos.x += item_width * rows;
2409 pos.x += item_width * rows;
2410 if (rows == 2 && column == columns - 1) {
2418 panel_size.x += panel_bg_padding * 2; // restore initial width
2423 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
2425 pos.x += hud_fontsize.x * 0.25;
2426 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2427 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
2428 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2430 pos.y += hud_fontsize.y;
2436 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
2437 float stat_secrets_found, stat_secrets_total;
2438 float stat_monsters_killed, stat_monsters_total;
2442 // get monster stats
2443 stat_monsters_killed = STAT(MONSTERS_KILLED);
2444 stat_monsters_total = STAT(MONSTERS_TOTAL);
2446 // get secrets stats
2447 stat_secrets_found = STAT(SECRETS_FOUND);
2448 stat_secrets_total = STAT(SECRETS_TOTAL);
2450 // get number of rows
2451 if(stat_secrets_total)
2453 if(stat_monsters_total)
2456 // if no rows, return
2460 // draw table header
2461 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2462 pos.y += 1.25 * hud_fontsize.y;
2463 if(panel.current_panel_bg != "0")
2464 pos.y += panel_bg_border;
2467 panel_size.y = hud_fontsize.y * rows;
2468 panel_size.y += panel_bg_padding * 2;
2471 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2472 if(panel.current_panel_bg != "0")
2473 end_pos.y += panel_bg_border * 2;
2475 if(panel_bg_padding)
2477 panel_pos += '1 1 0' * panel_bg_padding;
2478 panel_size -= '2 2 0' * panel_bg_padding;
2482 vector tmp = panel_size;
2485 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2488 if(stat_monsters_total)
2490 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2491 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2495 if(stat_secrets_total)
2497 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2498 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2501 panel_size.x += panel_bg_padding * 2; // restore initial width
2506 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2509 RANKINGS_RECEIVED_CNT = 0;
2510 for (i=RANKINGS_CNT-1; i>=0; --i)
2512 ++RANKINGS_RECEIVED_CNT;
2514 if (RANKINGS_RECEIVED_CNT == 0)
2517 vector hl_rgb = rgb + '0.5 0.5 0.5';
2519 vector scoreboard_selected_hl_pos = pos;
2521 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2522 pos.y += 1.25 * hud_fontsize.y;
2523 if(panel.current_panel_bg != "0")
2524 pos.y += panel_bg_border;
2526 vector scoreboard_selected_hl_size = '0 0 0';
2527 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2528 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2533 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2535 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2540 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2542 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2546 float ranksize = 3 * hud_fontsize.x;
2547 float timesize = 5 * hud_fontsize.x;
2548 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2549 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2550 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2553 rankings_cnt = RANKINGS_RECEIVED_CNT;
2554 rankings_rows = ceil(rankings_cnt / rankings_columns);
2557 // expand name column to fill the entire row
2558 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2559 namesize += available_space;
2560 columnsize.x += available_space;
2562 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2563 panel_size.y += panel_bg_padding * 2;
2564 scoreboard_selected_hl_size.y += panel_size.y;
2568 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2569 if(panel.current_panel_bg != "0")
2570 end_pos.y += panel_bg_border * 2;
2572 if(panel_bg_padding)
2574 panel_pos += '1 1 0' * panel_bg_padding;
2575 panel_size -= '2 2 0' * panel_bg_padding;
2581 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2583 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2585 int column = 0, j = 0;
2586 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2587 int start_item = rankings_start_column * rankings_rows;
2588 for(i = start_item; i < start_item + rankings_cnt; ++i)
2590 int t = grecordtime[i];
2594 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2595 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2596 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2597 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2599 str = count_ordinal(i+1);
2600 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2601 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2602 str = ColorTranslateRGB(grecordholder[i]);
2604 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2605 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2607 pos.y += 1.25 * hud_fontsize.y;
2609 if(j >= rankings_rows)
2613 pos.x += panel_size.x / rankings_columns;
2614 pos.y = panel_pos.y;
2617 strfree(zoned_name_self);
2619 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2621 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2622 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2625 panel_size.x += panel_bg_padding * 2; // restore initial width
2629 bool have_weapon_stats;
2630 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2632 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2634 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2637 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2638 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2644 if (!have_weapon_stats)
2646 FOREACH(Weapons, it != WEP_Null, {
2647 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2648 if (weapon_stats >= 0)
2650 have_weapon_stats = true;
2654 if (!have_weapon_stats)
2661 bool have_item_stats;
2662 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2664 Inventory g_inventory = inventoryslots[current_player];
2666 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2668 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2670 if (gametype == MAPINFO_TYPE_DUEL) // z411 : We already show items in our duel scoreboard.
2673 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2674 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2680 if (!have_item_stats)
2682 IL_EACH(default_order_items, true, {
2683 if (!is_item_filtered(it))
2685 int q = g_inventory.inv_items[it.m_id];
2686 //q = 1; // debug: display all items
2689 have_item_stats = true;
2694 if (!have_item_stats)
2701 vector Scoreboard_Spectators_Draw(vector pos) {
2706 for(pl = players.sort_next; pl; pl = pl.sort_next)
2708 if(pl.team == NUM_SPECTATOR)
2710 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2711 if(tm.team == NUM_SPECTATOR)
2713 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2714 draw_beginBoldFont();
2715 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2717 pos.y += 1.25 * hud_fontsize.y;
2719 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2720 pos.y += 1.25 * hud_fontsize.y;
2725 if (str != "") // if there's at least one spectator
2726 pos.y += 0.5 * hud_fontsize.y;
2731 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2733 string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2734 int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2735 return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2736 (s_label == "score") ? CTX(_("SCO^points")) :
2737 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2740 void Scoreboard_Draw()
2742 if(!autocvar__hud_configure)
2744 if(!hud_draw_maximized) return;
2746 // frametime checks allow to toggle the scoreboard even when the game is paused
2747 if(scoreboard_active) {
2748 if (scoreboard_fade_alpha == 0)
2749 scoreboard_time = time;
2750 if(hud_configure_menu_open == 1)
2751 scoreboard_fade_alpha = 1;
2752 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2753 if (scoreboard_fadeinspeed && frametime)
2754 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2756 scoreboard_fade_alpha = 1;
2757 if(hud_fontsize_str != autocvar_hud_fontsize)
2759 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2760 Scoreboard_initFieldSizes();
2761 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2765 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2766 if (scoreboard_fadeoutspeed && frametime)
2767 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2769 scoreboard_fade_alpha = 0;
2772 if (!scoreboard_fade_alpha)
2774 scoreboard_acc_fade_alpha = 0;
2775 scoreboard_itemstats_fade_alpha = 0;
2780 scoreboard_fade_alpha = 0;
2782 if (autocvar_hud_panel_scoreboard_dynamichud)
2785 HUD_Scale_Disable();
2787 if(scoreboard_fade_alpha <= 0)
2789 panel_fade_alpha *= scoreboard_fade_alpha;
2790 HUD_Panel_LoadCvars();
2792 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2793 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2794 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2795 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2796 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2797 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2798 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2800 // don't overlap with con_notify
2801 if(!autocvar__hud_configure)
2802 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2804 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2805 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2806 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2807 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2808 panel_pos.x = scoreboard_left;
2809 panel_size.x = fixed_scoreboard_width;
2811 Scoreboard_UpdatePlayerTeams();
2813 scoreboard_top = panel_pos.y;
2814 vector pos = panel_pos;
2819 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2821 // Begin of Game Info Section
2822 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2823 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2826 //drawcolorcodedstring(pos, "bienvenidoainternet.org", sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2827 //drawpic_aspect(pos + '1 0 0' * (panel_size.x - 150), "gfx/bai_logo", vec2(150, sb_gameinfo_type_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2828 //pos.y += sb_gameinfo_type_fontsize.y;
2830 // Game Info: Game Type
2831 if (scoreboard_ui_enabled == 2)
2832 str = _("Team Selection");
2834 str = MapInfo_Type_ToText(gametype);
2835 draw_beginBoldFont();
2836 //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);
2837 drawcolorcodedstring(pos, str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2840 vector tmp_old_sz = draw_getimagesize("gfx/bai_logo");
2841 float tmp_aspect = tmp_old_sz.x/tmp_old_sz.y;
2842 vector tmp_new_sz = vec2(sb_gameinfo_type_fontsize.y * tmp_aspect, sb_gameinfo_type_fontsize.y);
2845 drawpic(pos + '1 0 0' * (panel_size.x - tmp_new_sz.x), "gfx/bai_logo", tmp_new_sz, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2847 pos.y += sb_gameinfo_type_fontsize.y;
2850 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth_colors(hostname_full, sb_gameinfo_detail_fontsize)), hostname_full, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2852 pos.y += sb_gameinfo_detail_fontsize.y;
2854 // Game Info: Game Detail
2855 if (scoreboard_ui_enabled == 2)
2857 if (scoreboard_selected_team)
2858 str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2860 str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2861 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);
2863 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2864 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2865 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);
2869 float tl = STAT(TIMELIMIT);
2870 float fl = STAT(FRAGLIMIT);
2871 float ll = STAT(LEADLIMIT);
2872 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2875 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2876 if(!gametype.m_hidelimits)
2881 str = strcat(str, "^7 / "); // delimiter
2882 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2886 if(tl > 0 || fl > 0)
2889 if (ll_and_fl && fl > 0)
2890 str = strcat(str, "^7 & ");
2892 str = strcat(str, "^7 / ");
2894 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2898 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
2899 // map name and player count
2903 str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2904 str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
2905 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2907 // End of Game Info Section
2909 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2910 if(panel.current_panel_bg != "0")
2911 pos.y += panel_bg_border;
2913 // Draw the scoreboard
2914 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2917 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2921 vector panel_bg_color_save = panel_bg_color;
2922 vector team_score_baseoffset;
2923 vector team_size_baseoffset;
2924 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2926 // put team score to the left of scoreboard (and team size to the right)
2927 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 1.5;
2928 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2929 if(panel.current_panel_bg != "0")
2931 team_score_baseoffset.x -= panel_bg_border;
2932 team_size_baseoffset.x += panel_bg_border;
2937 // put team score to the right of scoreboard (and team size to the left)
2938 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 1.5;
2939 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2940 if(panel.current_panel_bg != "0")
2942 team_score_baseoffset.x += panel_bg_border;
2943 team_size_baseoffset.x -= panel_bg_border;
2947 int team_size_total = 0;
2948 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2950 // calculate team size total (sum of all team sizes)
2951 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2952 if(tm.team != NUM_SPECTATOR)
2953 team_size_total += tm.team_size;
2956 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2958 if(tm.team == NUM_SPECTATOR)
2963 vector rgb = Team_ColorRGB(tm.team);
2964 /*draw_beginBoldFont();
2965 str = ftos(tm.(teamscores(ts_primary)));
2966 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2968 // team score on the left (default)
2969 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 3);
2973 // team score on the right
2974 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 3);
2976 drawstring(str_pos, str, hud_fontsize * 3, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2978 // team size (if set to show on the side)
2979 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2981 // calculate the starting position for the whole team size info string
2982 str = sprintf("%d/%d", tm.team_size, team_size_total);
2983 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2985 // team size on the left
2986 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2990 // team size on the right
2991 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2993 str = sprintf("%d", tm.team_size);
2994 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2995 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2996 str = sprintf("/%d", team_size_total);
2997 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
3001 // secondary score, e.g. keyhunt
3002 if(ts_primary != ts_secondary)
3004 str = ftos(tm.(teamscores(ts_secondary)));
3005 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
3008 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
3013 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
3016 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
3021 // z411 My team header
3023 drawfill(pos, team_score_size, rgb * 0.5, sbt_highlight_alpha, DRAWFLAG_NORMAL);
3026 str = ftos(tm.(teamscores(ts_primary)));
3028 str_pos.x += (team_score_size.x / 2) - (stringwidth(str, true, team_score_fontsize) / 2);
3029 str_pos.y += (team_score_size.y / 2) - (team_score_fontsize.y / 2);
3031 draw_beginBoldFont();
3032 drawstring(str_pos, str, team_score_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
3036 str = Team_CustomName(tm.team);
3038 str_pos.x += team_score_size.x + team_name_fontsize.x * 0.5;
3039 str_pos.y += (team_score_size.y / 2) - (team_name_fontsize.y / 2);
3040 drawcolorcodedstring(str_pos, str, team_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
3042 pos.y += team_score_size.y + (hud_fontsize.y * 0.5);
3044 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
3045 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
3046 else if(panel_bg_color_team > 0)
3047 panel_bg_color = rgb * panel_bg_color_team;
3049 panel_bg_color = rgb;
3050 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
3052 panel_bg_color = panel_bg_color_save;
3054 else if(gametype == MAPINFO_TYPE_DUEL)
3056 for(tm = teams.sort_next; tm; tm = tm.sort_next)
3057 if(tm.team != NUM_SPECTATOR)
3060 // z411 make DUEL TABLE
3061 pos = Scoreboard_MakeDuelTable(pos, tm, panel_bg_color, bg_size);
3065 for(tm = teams.sort_next; tm; tm = tm.sort_next)
3066 if(tm.team != NUM_SPECTATOR)
3069 // display it anyway
3070 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
3073 pos = Scoreboard_MedalStats_Draw(pos);
3075 // draw scoreboard spectators before accuracy and item stats
3076 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
3077 pos = Scoreboard_Spectators_Draw(pos);
3080 // draw accuracy and item stats
3081 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
3082 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
3083 if (Scoreboard_ItemStats_WouldDraw(pos.y))
3084 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
3086 // draw scoreboard spectators after accuracy and item stats and before rankings
3087 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
3088 pos = Scoreboard_Spectators_Draw(pos);
3091 if(MUTATOR_CALLHOOK(ShowRankings)) {
3092 string ranktitle = M_ARGV(0, string);
3093 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
3094 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
3095 if(race_speedaward_alltimebest)
3098 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
3102 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
3103 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
3104 str = strcat(str, " / ");
3106 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
3107 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
3108 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
3109 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
3111 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
3116 // draw scoreboard spectators after rankings
3117 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
3118 pos = Scoreboard_Spectators_Draw(pos);
3121 //pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
3123 // draw scoreboard spectators after mapstats
3124 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
3125 pos = Scoreboard_Spectators_Draw(pos);
3129 // print information about respawn status
3130 float respawn_time = STAT(RESPAWN_TIME);
3131 if(!intermission && respawn_time)
3133 if(respawn_time < 0)
3135 // a negative number means we are awaiting respawn, time value is still the same
3136 respawn_time *= -1; // remove mark now that we checked it
3138 if(respawn_time < time) // it happens for a few frames when server is respawning the player
3139 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
3141 str = sprintf(_("^1Respawning in ^3%s^1..."),
3142 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
3143 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
3145 count_seconds(ceil(respawn_time - time))
3149 else if(time < respawn_time)
3151 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
3152 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
3153 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
3155 count_seconds(ceil(respawn_time - time))
3159 else if(time >= respawn_time)
3160 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
3162 pos.y += 1.2 * hud_fontsize.y;
3163 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
3166 pos.y += hud_fontsize.y;
3167 if (scoreboard_fade_alpha < 1)
3168 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
3169 else if (pos.y != scoreboard_bottom)
3171 if (pos.y > scoreboard_bottom)
3172 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
3174 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
3179 if (scoreboard_fade_alpha == 1)
3181 if (scoreboard_bottom > 0.95 * vid_conheight)
3182 rankings_rows = max(1, rankings_rows - 1);
3183 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
3184 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
3186 rankings_cnt = rankings_rows * rankings_columns;