]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Scoreboard UI and team selection: add SHIFT+TAB shortcuts to reverse cycling
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / scoreboard.qc
1 #include "scoreboard.qh"
2
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
18
19 // Scoreboard (#24)
20
21 void Scoreboard_Draw_Export(int fh)
22 {
23         // allow saving cvars that aesthetically change the panel into hud skin files
24         HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25         HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26         HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27         HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28         HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29         HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30         HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35         HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36         HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37         HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38         HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
39 }
40
41 const int MAX_SBT_FIELDS = MAX_SCORE;
42
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 int sbt_num_fields;
47
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
50 float max_namesize;
51
52 float sbt_bg_alpha;
53 float sbt_fg_alpha;
54 float sbt_fg_alpha_self;
55 bool sbt_highlight;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
59
60 // provide basic panel cvars to old clients
61 // TODO remove them after a future release (0.8.2+)
62 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
63 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
64 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
65 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
66 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
67 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
68 noref string autocvar_hud_panel_scoreboard_bg_border = "";
69 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
70
71 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
72 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
73 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
74 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
75 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
78 bool autocvar_hud_panel_scoreboard_table_highlight = true;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
85 float autocvar_hud_panel_scoreboard_spectators_position = 1;
86
87 bool autocvar_hud_panel_scoreboard_accuracy = true;
88 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
91 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92
93 bool autocvar_hud_panel_scoreboard_itemstats = true;
94 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
95 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
96 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
97 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
98 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
99
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
101
102 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
103 bool autocvar_hud_panel_scoreboard_others_showscore = true;
104 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
105 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
106 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
107 bool autocvar_hud_panel_scoreboard_playerid = false;
108 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
109 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
110
111 float scoreboard_time;
112
113 // mode 0: returns translated label
114 // mode 1: prints name and description of all the labels
115 string Label_getInfo(string label, int mode)
116 {
117         if (mode == 1)
118                 label = "bckills"; // first case in the switch
119
120         switch(label)
121         {
122                 case "bckills":      if (!mode) return CTX(_("SCO^bckills"));      else LOG_HELP(strcat("^3", "bckills", "            ^7", _("Number of ball carrier kills")));
123                 case "bctime":       if (!mode) return CTX(_("SCO^bctime"));       else LOG_HELP(strcat("^3", "bctime", "             ^7", _("Total amount of time holding the ball in Keepaway")));
124                 case "caps":         if (!mode) return CTX(_("SCO^caps"));         else LOG_HELP(strcat("^3", "caps", "               ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
125                 case "captime":      if (!mode) return CTX(_("SCO^captime"));      else LOG_HELP(strcat("^3", "captime", "            ^7", _("Time of fastest capture (CTF)")));
126                 case "deaths":       if (!mode) return CTX(_("SCO^deaths"));       else LOG_HELP(strcat("^3", "deaths", "             ^7", _("Number of deaths")));
127                 case "destroyed":    if (!mode) return CTX(_("SCO^destroyed"));    else LOG_HELP(strcat("^3", "destroyed", "          ^7", _("Number of keys destroyed by pushing them into void")));
128                 case "dmg":          if (!mode) return CTX(_("SCO^damage"));       else LOG_HELP(strcat("^3", "dmg", "                ^7", _("The total damage done")));
129                 case "dmgtaken":     if (!mode) return CTX(_("SCO^dmgtaken"));     else LOG_HELP(strcat("^3", "dmgtaken", "           ^7", _("The total damage taken")));
130                 case "drops":        if (!mode) return CTX(_("SCO^drops"));        else LOG_HELP(strcat("^3", "drops", "              ^7", _("Number of flag drops")));
131                 case "elo":          if (!mode) return CTX(_("SCO^elo"));          else LOG_HELP(strcat("^3", "elo", "                ^7", _("Player ELO")));
132                 case "fastest":      if (!mode) return CTX(_("SCO^fastest"));      else LOG_HELP(strcat("^3", "fastest", "            ^7", _("Time of fastest lap (Race/CTS)")));
133                 case "faults":       if (!mode) return CTX(_("SCO^faults"));       else LOG_HELP(strcat("^3", "faults", "             ^7", _("Number of faults committed")));
134                 case "fckills":      if (!mode) return CTX(_("SCO^fckills"));      else LOG_HELP(strcat("^3", "fckills", "            ^7", _("Number of flag carrier kills")));
135                 case "fps":          if (!mode) return CTX(_("SCO^fps"));          else LOG_HELP(strcat("^3", "fps", "                ^7", _("FPS")));
136                 case "frags":        if (!mode) return CTX(_("SCO^frags"));        else LOG_HELP(strcat("^3", "frags", "              ^7", _("Number of kills minus suicides")));
137                 case "goals":        if (!mode) return CTX(_("SCO^goals"));        else LOG_HELP(strcat("^3", "goals", "              ^7", _("Number of goals scored")));
138                 case "kckills":      if (!mode) return CTX(_("SCO^kckills"));      else LOG_HELP(strcat("^3", "kckills", "            ^7", _("Number of keys carrier kills")));
139                 case "kd":           if (!mode) return CTX(_("SCO^k/d"));          else LOG_HELP(strcat("^3", "kd", "                 ^7", _("The kill-death ratio")));
140                 case "kdr":          if (!mode) return CTX(_("SCO^kdr"));          else LOG_HELP(strcat("^3", "kdr", "                ^7", _("The kill-death ratio")));
141                 case "kdratio":      if (!mode) return CTX(_("SCO^kdratio"));      else LOG_HELP(strcat("^3", "kdratio", "            ^7", _("The kill-death ratio")));
142                 case "kills":        if (!mode) return CTX(_("SCO^kills"));        else LOG_HELP(strcat("^3", "kills", "              ^7", _("Number of kills")));
143                 case "laps":         if (!mode) return CTX(_("SCO^laps"));         else LOG_HELP(strcat("^3", "laps", "               ^7", _("Number of laps finished (Race/CTS)")));
144                 case "lives":        if (!mode) return CTX(_("SCO^lives"));        else LOG_HELP(strcat("^3", "lives", "              ^7", _("Number of lives (LMS)")));
145                 case "losses":       if (!mode) return CTX(_("SCO^losses"));       else LOG_HELP(strcat("^3", "losses", "             ^7", _("Number of times a key was lost")));
146                 case "name":         if (!mode) return CTX(_("SCO^name"));         else LOG_HELP(strcat("^3", "name", "               ^7", _("Player name")));
147                 case "nick":         if (!mode) return CTX(_("SCO^nick"));         else LOG_HELP(strcat("^3", "nick", "               ^7", _("Player name")));
148                 case "objectives":   if (!mode) return CTX(_("SCO^objectives"));   else LOG_HELP(strcat("^3", "objectives", "         ^7", _("Number of objectives destroyed")));
149                 case "pickups":      if (!mode) return CTX(_("SCO^pickups"));      else LOG_HELP(strcat("^3", "pickups", "            ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
150                 case "ping":         if (!mode) return CTX(_("SCO^ping"));         else LOG_HELP(strcat("^3", "ping", "               ^7", _("Ping time")));
151                 case "pl":           if (!mode) return CTX(_("SCO^pl"));           else LOG_HELP(strcat("^3", "pl", "                 ^7", _("Packet loss")));
152                 case "pushes":       if (!mode) return CTX(_("SCO^pushes"));       else LOG_HELP(strcat("^3", "pushes", "             ^7", _("Number of players pushed into void")));
153                 case "rank":         if (!mode) return CTX(_("SCO^rank"));         else LOG_HELP(strcat("^3", "rank", "               ^7", _("Player rank")));
154                 case "returns":      if (!mode) return CTX(_("SCO^returns"));      else LOG_HELP(strcat("^3", "returns", "            ^7", _("Number of flag returns")));
155                 case "revivals":     if (!mode) return CTX(_("SCO^revivals"));     else LOG_HELP(strcat("^3", "revivals", "           ^7", _("Number of revivals")));
156                 case "rounds":       if (!mode) return CTX(_("SCO^rounds won"));   else LOG_HELP(strcat("^3", "rounds", "             ^7", _("Number of rounds won")));
157                 case "score":        if (!mode) return CTX(_("SCO^score"));        else LOG_HELP(strcat("^3", "score", "              ^7", _("Total score")));
158                 case "suicides":     if (!mode) return CTX(_("SCO^suicides"));     else LOG_HELP(strcat("^3", "suicides", "           ^7", _("Number of suicides")));
159                 case "sum":          if (!mode) return CTX(_("SCO^sum"));          else LOG_HELP(strcat("^3", "sum", "                ^7", _("Number of kills minus deaths")));
160                 case "takes":        if (!mode) return CTX(_("SCO^takes"));        else LOG_HELP(strcat("^3", "takes", "              ^7", _("Number of domination points taken (Domination)")));
161                 case "teamkills":    if (!mode) return CTX(_("SCO^teamkills"));    else LOG_HELP(strcat("^3", "teamkills", "          ^7", _("Number of teamkills")));
162                 case "ticks":        if (!mode) return CTX(_("SCO^ticks"));        else LOG_HELP(strcat("^3", "ticks", "              ^7", _("Number of ticks (Domination)")));
163                 case "time":         if (!mode) return CTX(_("SCO^time"));         else LOG_HELP(strcat("^3", "time", "               ^7", _("Total time raced (Race/CTS)")));
164                 default: return label;
165         }
166         return label;
167 }
168
169 void HUD_Scoreboard_UI_Disable()
170 {
171         scoreboard_showscores = false;
172         scoreboard_ui_enabled = 0;
173         scoreboard_selected_panel = 0;
174         scoreboard_selected_player = NULL;
175         scoreboard_selected_team = NULL;
176 }
177
178 // mode: 0 normal, 1 team selection
179 void Scoreboard_UI_Enable(int mode)
180 {
181         if (mode == 1)
182         {
183                 if (scoreboard_ui_enabled == 2 || !teamplay)
184                         return;
185
186                 // release player's pressed keys as they aren't released elsewhere
187                 // in particular jump needs to be released as it may open the team selection
188                 // (when server detects jump has been pressed it sends the command to open the team selection)
189                 Release_Common_Keys();
190                 scoreboard_ui_enabled = 2;
191                 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
192         }
193         else
194         {
195                 if (scoreboard_ui_enabled == 1)
196                         return;
197                 scoreboard_ui_enabled = 1;
198                 scoreboard_selected_panel = SB_PANEL_FIRST;
199         }
200         scoreboard_selected_player = NULL;
201         scoreboard_selected_team = NULL;
202         scoreboard_selected_panel_time = time;
203 }
204
205 int rankings_start_column;
206 int rankings_rows = 0;
207 int rankings_columns = 0;
208 int rankings_cnt = 0;
209 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
210 {
211         string s;
212
213         if(!scoreboard_ui_enabled)
214                 return false;
215
216         if(bInputType == 3)
217         {
218                 mousepos.x = nPrimary;
219                 mousepos.y = nSecondary;
220                 return true;
221         }
222
223         if(bInputType == 2)
224                 return false;
225
226         // at this point bInputType can only be 0 or 1 (key pressed or released)
227         bool key_pressed = (bInputType == 0);
228
229         // ESC to exit (TAB-ESC works too)
230         if(nPrimary == K_ESCAPE)
231         {
232                 if (!key_pressed)
233                         return true;
234                 HUD_Scoreboard_UI_Disable();
235                 return true;
236         }
237
238         // block any input while a menu dialog is fading
239         if(autocvar__menu_alpha)
240         {
241                 hudShiftState = 0;
242                 return true;
243         }
244
245         // allow console bind to work
246         string con_keys = findkeysforcommand("toggleconsole", 0);
247         int keys = tokenize(con_keys); // findkeysforcommand returns data for this
248
249         bool hit_con_bind = false;
250         int i;
251         for (i = 0; i < keys; ++i)
252         {
253                 if(nPrimary == stof(argv(i)))
254                         hit_con_bind = true;
255         }
256
257         if(key_pressed) {
258                 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
259                 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
260                 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
261                 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
262         }
263         else {
264                 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
265                 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
266                 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
267                 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
268         }
269
270         if(nPrimary == K_TAB)
271         {
272                 if (!key_pressed)
273                         return true;
274                 if (scoreboard_ui_enabled == 2)
275                 {
276                         if (hudShiftState & S_SHIFT)
277                                 goto uparrow_action;
278                         else
279                                 goto downarrow_action;
280                 }
281
282                 if (hudShiftState & S_SHIFT)
283                 {
284                         --scoreboard_selected_panel;
285                         if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
286                                 --scoreboard_selected_panel;
287                         if (scoreboard_selected_panel < SB_PANEL_FIRST)
288                                 scoreboard_selected_panel = SB_PANEL_MAX;
289                 }
290                 else
291                 {
292                         ++scoreboard_selected_panel;
293                         if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
294                                 ++scoreboard_selected_panel;
295                         if (scoreboard_selected_panel > SB_PANEL_MAX)
296                                 scoreboard_selected_panel = SB_PANEL_FIRST;
297                 }
298
299                 scoreboard_selected_panel_time = time;
300         }
301         else if(nPrimary == K_DOWNARROW)
302         {
303                 if (!key_pressed)
304                         return true;
305                 LABEL(downarrow_action);
306                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
307                 {
308                         if (scoreboard_ui_enabled == 2)
309                         {
310                                 entity curr_team = NULL;
311                                 bool scoreboard_selected_team_found = false;
312                                 if (!scoreboard_selected_team)
313                                         scoreboard_selected_team_found = true;
314
315                                 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
316                                 {
317                                         if(tm.team == NUM_SPECTATOR)
318                                                 continue;
319                                         curr_team = tm;
320                                         if (scoreboard_selected_team_found)
321                                                 goto ok_team;
322                                         if (scoreboard_selected_team == tm)
323                                                 scoreboard_selected_team_found = true;
324                                 }
325                                 LABEL(ok_team);
326                                 if (curr_team == scoreboard_selected_team) // loop reached the last team
327                                         curr_team = NULL;
328                                 scoreboard_selected_team = curr_team;
329                         }
330                         else
331                         {
332                                 entity pl, tm;
333                                 entity curr_pl = NULL;
334                                 bool scoreboard_selected_player_found = false;
335                                 if (!scoreboard_selected_player)
336                                         scoreboard_selected_player_found = true;
337
338                                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
339                                 {
340                                         if(tm.team != NUM_SPECTATOR)
341                                         for(pl = players.sort_next; pl; pl = pl.sort_next)
342                                         {
343                                                 if(pl.team != tm.team)
344                                                         continue;
345                                                 curr_pl = pl;
346                                                 if (scoreboard_selected_player_found)
347                                                         goto ok_done;
348                                                 if (scoreboard_selected_player == pl)
349                                                         scoreboard_selected_player_found = true;
350                                         }
351                                 }
352                                 LABEL(ok_done);
353                                 if (curr_pl == scoreboard_selected_player) // loop reached the last player
354                                         curr_pl = NULL;
355                                 scoreboard_selected_player = curr_pl;
356                         }
357                 }
358         }
359         else if(nPrimary == K_UPARROW)
360         {
361                 if (!key_pressed)
362                         return true;
363                 LABEL(uparrow_action);
364                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
365                 {
366                         if (scoreboard_ui_enabled == 2)
367                         {
368                                 entity prev_team = NULL;
369                                 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
370                                 {
371                                         if(tm.team == NUM_SPECTATOR)
372                                                 continue;
373                                         if (tm == scoreboard_selected_team)
374                                                 goto ok_team2;
375                                         prev_team = tm;
376                                 }
377                                 LABEL(ok_team2);
378                                 scoreboard_selected_team = prev_team;
379                         }
380                         else
381                         {
382                                 entity prev_pl = NULL;
383                                 entity pl, tm;
384                                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
385                                 {
386                                         if(tm.team != NUM_SPECTATOR)
387                                         for(pl = players.sort_next; pl; pl = pl.sort_next)
388                                         {
389                                                 if(pl.team != tm.team)
390                                                         continue;
391                                                 if (pl == scoreboard_selected_player)
392                                                         goto ok_done2;
393                                                 prev_pl = pl;
394                                         }
395                                 }
396                                 LABEL(ok_done2);
397                                 scoreboard_selected_player = prev_pl;
398                         }
399                 }
400         }
401         else if(nPrimary == K_RIGHTARROW)
402         {
403                 if (!key_pressed)
404                         return true;
405                 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
406                         rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
407         }
408         else if(nPrimary == K_LEFTARROW)
409         {
410                 if (!key_pressed)
411                         return true;
412                 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
413                         rankings_start_column = max(rankings_start_column - 1, 0);
414         }
415         else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
416         {
417                 if (!key_pressed)
418                         return true;
419                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
420                 {
421                         if (scoreboard_ui_enabled == 2)
422                         {
423                                 string team_name;
424                                 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
425                                         team_name = "auto";
426                                 else
427                                         team_name = Static_Team_ColorName(scoreboard_selected_team.team);
428                                 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
429                                 HUD_Scoreboard_UI_Disable();
430                         }
431                         else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
432                         {
433                                 localcmd("join\n");
434                                 HUD_Scoreboard_UI_Disable();
435                         }
436                         else
437                                 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
438                 }
439         }
440         else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
441         {
442                 if (!key_pressed)
443                         return true;
444                 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
445                 {
446                         switch (scoreboard_selected_columns_layout)
447                         {
448                                 case 0:
449                                         if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
450                                         {
451                                                 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
452                                                 scoreboard_selected_columns_layout = 1;
453                                                 break;
454                                         }
455                                         // fallthrough
456                                 case 1:
457                                         localcmd(sprintf("scoreboard_columns_set default\n"));
458                                         scoreboard_selected_columns_layout = 2;
459                                         break;
460                                 case 2:
461                                         localcmd(sprintf("scoreboard_columns_set all\n"));
462                                         scoreboard_selected_columns_layout = 0;
463                                         break;
464                         }
465                 }
466         }
467         else if(nPrimary == 't' && (hudShiftState & S_CTRL))
468         {
469                 if (!key_pressed)
470                         return true;
471                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
472                 {
473                         if (scoreboard_selected_player)
474                         {
475                                 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
476                                 HUD_Scoreboard_UI_Disable();
477                         }
478                 }
479         }
480         else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
481         {
482                 if (!key_pressed)
483                         return true;
484                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
485                 {
486                         if (scoreboard_selected_player)
487                                 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
488                 }
489         }
490         else if(hit_con_bind || nPrimary == K_PAUSE)
491                 return false;
492
493         return true;
494 }
495
496 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
497 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
498
499 #define SB_EXTRA_SORTING_FIELDS 5
500 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
501 void Scoreboard_InitScores()
502 {
503         int i, f;
504
505         ps_primary = ps_secondary = NULL;
506         ts_primary = ts_secondary = -1;
507         FOREACH(Scores, true, {
508                 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
509                 if(f == SFL_SORT_PRIO_PRIMARY)
510                         ps_primary = it;
511                 if(f == SFL_SORT_PRIO_SECONDARY)
512                         ps_secondary = it;
513                 if(ps_primary == it || ps_secondary == it)
514                         continue;
515                 if (scores_label(it) == "kills")      sb_extra_sorting_field[0] = it;
516                 if (scores_label(it) == "deaths")     sb_extra_sorting_field[1] = it;
517                 if (scores_label(it) == "suicides")   sb_extra_sorting_field[2] = it;
518                 if (scores_label(it) == "dmg")        sb_extra_sorting_field[3] = it;
519                 if (scores_label(it) == "dmgtaken")   sb_extra_sorting_field[4] = it;
520         });
521         if(ps_secondary == NULL)
522                 ps_secondary = ps_primary;
523
524         for(i = 0; i < MAX_TEAMSCORE; ++i)
525         {
526                 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
527                 if(f == SFL_SORT_PRIO_PRIMARY)
528                         ts_primary = i;
529                 if(f == SFL_SORT_PRIO_SECONDARY)
530                         ts_secondary = i;
531         }
532         if(ts_secondary == -1)
533                 ts_secondary = ts_primary;
534
535         Cmd_Scoreboard_SetFields(0);
536 }
537
538 //float lastpnum;
539 void Scoreboard_UpdatePlayerTeams()
540 {
541         static float update_time;
542         if (time <= update_time)
543                 return;
544         update_time = time;
545
546         entity pl, tmp;
547         //int num = 0;
548         for(pl = players.sort_next; pl; pl = pl.sort_next)
549         {
550                 //num += 1;
551                 int Team = entcs_GetScoreTeam(pl.sv_entnum);
552                 if(SetTeam(pl, Team))
553                 {
554                         tmp = pl.sort_prev;
555                         Scoreboard_UpdatePlayerPos(pl);
556                         if(tmp)
557                                 pl = tmp;
558                         else
559                                 pl = players.sort_next;
560                 }
561         }
562         /*
563         if(num != lastpnum)
564                 print(strcat("PNUM: ", ftos(num), "\n"));
565         lastpnum = num;
566         */
567 }
568
569 int Scoreboard_CompareScore(int vl, int vr, int f)
570 {
571         TC(int, vl); TC(int, vr); TC(int, f);
572         if(f & SFL_ZERO_IS_WORST)
573         {
574                 if(vl == 0 && vr != 0)
575                         return 1;
576                 if(vl != 0 && vr == 0)
577                         return 0;
578         }
579         if(vl > vr)
580                 return IS_INCREASING(f);
581         if(vl < vr)
582                 return IS_DECREASING(f);
583         return -1;
584 }
585
586 float Scoreboard_ComparePlayerScores(entity left, entity right)
587 {
588         int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
589         int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
590
591         if(vl > vr)
592                 return true;
593         if(vl < vr)
594                 return false;
595
596         if(vl == NUM_SPECTATOR)
597         {
598                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
599                 // no other sorting
600                 if(!left.gotscores && right.gotscores)
601                         return true;
602                 return false;
603         }
604
605         entity fld = NULL;
606         int r;
607         for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
608         {
609                 if (i < 0)
610                 {
611                         if (!fld) fld = ps_primary;
612                         else if (ps_secondary == ps_primary) continue;
613                         else fld = ps_secondary;
614                 }
615                 else
616                 {
617                         fld = sb_extra_sorting_field[i];
618                         if (fld == ps_primary || fld == ps_secondary) continue;
619                 }
620                 if (!fld) continue;
621
622                 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
623                 if (r >= 0) return r;
624         }
625
626         if (left.sv_entnum < right.sv_entnum)
627                 return true;
628
629         return false;
630 }
631
632 void Scoreboard_UpdatePlayerPos(entity player)
633 {
634         entity ent;
635         for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
636         {
637                 SORT_SWAP(player, ent);
638         }
639         for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
640         {
641                 SORT_SWAP(ent, player);
642         }
643 }
644
645 float Scoreboard_CompareTeamScores(entity left, entity right)
646 {
647         if(left.team == NUM_SPECTATOR)
648                 return 1;
649         if(right.team == NUM_SPECTATOR)
650                 return 0;
651
652         int fld_idx = -1;
653         int r;
654         for(int i = -2; i < MAX_TEAMSCORE; ++i)
655         {
656                 if (i < 0)
657                 {
658                         if (fld_idx == -1) fld_idx = ts_primary;
659                         else if (ts_secondary == ts_primary) continue;
660                         else fld_idx = ts_secondary;
661                 }
662                 else
663                 {
664                         fld_idx = i;
665                         if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
666                 }
667
668                 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
669                 if (r >= 0) return r;
670         }
671
672         if (left.team < right.team)
673                 return true;
674
675         return false;
676 }
677
678 void Scoreboard_UpdateTeamPos(entity Team)
679 {
680         entity ent;
681         for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
682         {
683                 SORT_SWAP(Team, ent);
684         }
685         for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
686         {
687                 SORT_SWAP(ent, Team);
688         }
689 }
690
691 void Cmd_Scoreboard_Help()
692 {
693         LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
694         LOG_HELP(_("Usage:"));
695         LOG_HELP("^2scoreboard_columns_set ^3default");
696         LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
697         LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
698         LOG_HELP(_("  ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
699         LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
700         LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
701         LOG_HELP(_("The following field names are recognized (case insensitive):"));
702         LOG_HELP("");
703
704         PrintScoresLabels();
705         LOG_HELP("");
706
707         LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
708                 "of game types, then a slash, to make the field show up only in these\n"
709                 "or in all but these game types. You can also specify 'all' as a\n"
710                 "field to show all fields available for the current game mode."));
711         LOG_HELP("");
712
713         LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
714                 "include/exclude ALL teams/noteams game modes."));
715         LOG_HELP("");
716
717         LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
718         LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
719                 "right of the vertical bar aligned to the right."));
720         LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
721                         "other gamemodes except DM."));
722 }
723
724 // NOTE: adding a gametype with ? to not warn for an optional field
725 // make sure it's excluded in a previous exclusive rule, if any
726 // otherwise the previous exclusive rule warns anyway
727 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
728 #define SCOREBOARD_DEFAULT_COLUMNS \
729 "ping pl fps name |" \
730 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
731 " -teams,lms/deaths +ft,tdm/deaths" \
732 " +tdm/sum" \
733 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
734 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
735 " +tdm,ft,dom,ons,as/teamkills"\
736 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
737 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
738 " +lms/lives +lms/rank" \
739 " +kh/kckills +kh/losses +kh/caps" \
740 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
741 " +as/objectives +nb/faults +nb/goals" \
742 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
743 " +dom/ticks +dom/takes" \
744 " -lms,rc,cts,inv,nb/score"
745
746 void Cmd_Scoreboard_SetFields(int argc)
747 {
748         TC(int, argc);
749         int i, slash;
750         string str, pattern;
751         bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
752         int missing;
753
754         if(!gametype)
755                 return; // do nothing, we don't know gametype and scores yet
756
757         // sbt_fields uses strunzone on the titles!
758         if(!sbt_field_title[0])
759                 for(i = 0; i < MAX_SBT_FIELDS; ++i)
760                         sbt_field_title[i] = strzone("(null)");
761
762         // TODO: re enable with gametype dependant cvars?
763         if(argc < 3) // no arguments provided
764                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
765
766         if(argc < 3)
767                 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
768
769         if(argc == 3)
770         {
771                 if(argv(2) == "default" || argv(2) == "expand_default")
772                 {
773                         if(argv(2) == "expand_default")
774                                 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
775                         argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
776                 }
777                 else if(argv(2) == "all" || argv(2) == "ALL")
778                 {
779                         string s = "ping pl name |"; // scores without label (not really scores)
780                         if(argv(2) == "ALL")
781                         {
782                                 // scores without label
783                                 s = strcat(s, " ", "sum");
784                                 s = strcat(s, " ", "kdratio");
785                                 s = strcat(s, " ", "frags");
786                         }
787                         FOREACH(Scores, true, {
788                                 if(it != ps_primary)
789                                 if(it != ps_secondary)
790                                 if(scores_label(it) != "")
791                                         s = strcat(s, " ", scores_label(it));
792                         });
793                         if(ps_secondary != ps_primary)
794                                 s = strcat(s, " ", scores_label(ps_secondary));
795                         s = strcat(s, " ", scores_label(ps_primary));
796                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
797                 }
798         }
799
800
801         sbt_num_fields = 0;
802
803         hud_fontsize = HUD_GetFontsize("hud_fontsize");
804
805         for(i = 1; i < argc - 1; ++i)
806         {
807                 str = argv(i+1);
808                 bool nocomplain = false;
809                 if(substring(str, 0, 1) == "?")
810                 {
811                         nocomplain = true;
812                         str = substring(str, 1, strlen(str) - 1);
813                 }
814
815                 slash = strstrofs(str, "/", 0);
816                 if(slash >= 0)
817                 {
818                         pattern = substring(str, 0, slash);
819                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
820
821                         if (!isGametypeInFilter(gametype, teamplay, false, pattern))
822                                 continue;
823                 }
824
825                 str = strtolower(str);
826                 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
827                 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
828
829                 PlayerScoreField j;
830                 switch(str)
831                 {
832                         // fields without a label (not networked via the score system)
833                         case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
834                         case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
835                         case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
836                         case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
837                         case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
838                         case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
839                         case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
840                         default: // fields with a label
841                         {
842                                 // map alternative labels
843                                 if (str == "damage") str = "dmg";
844                                 if (str == "damagetaken") str = "dmgtaken";
845
846                                 FOREACH(Scores, true, {
847                                         if (str == strtolower(scores_label(it))) {
848                                                 j = it;
849                                                 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
850                                         }
851                                 });
852
853                                 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
854                                 if(!nocomplain && str != "fps") // server can disable the fps field
855                                         LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
856
857                                 strfree(sbt_field_title[sbt_num_fields]);
858                                 sbt_field_size[sbt_num_fields] = 0;
859                                 continue;
860
861                                 LABEL(found)
862                                 sbt_field[sbt_num_fields] = j;
863                                 if(j == ps_primary)
864                                         have_primary = true;
865                                 if(j == ps_secondary)
866                                         have_secondary = true;
867
868                         }
869                 }
870                 ++sbt_num_fields;
871                 if(sbt_num_fields >= MAX_SBT_FIELDS)
872                         break;
873         }
874
875         if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
876                 have_primary = true;
877         if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
878                 have_secondary = true;
879         if(ps_primary == ps_secondary)
880                 have_secondary = true;
881         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
882
883         if(sbt_num_fields + missing < MAX_SBT_FIELDS)
884         {
885                 if(!have_name)
886                 {
887                         strfree(sbt_field_title[sbt_num_fields]);
888                         for(i = sbt_num_fields; i > 0; --i)
889                         {
890                                 sbt_field_title[i] = sbt_field_title[i-1];
891                                 sbt_field_size[i] = sbt_field_size[i-1];
892                                 sbt_field[i] = sbt_field[i-1];
893                         }
894                         sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
895                         sbt_field[0] = SP_NAME;
896                         ++sbt_num_fields;
897                         LOG_INFO("fixed missing field 'name'");
898
899                         if(!have_separator)
900                         {
901                                 strfree(sbt_field_title[sbt_num_fields]);
902                                 for(i = sbt_num_fields; i > 1; --i)
903                                 {
904                                         sbt_field_title[i] = sbt_field_title[i-1];
905                                         sbt_field_size[i] = sbt_field_size[i-1];
906                                         sbt_field[i] = sbt_field[i-1];
907                                 }
908                                 sbt_field_title[1] = strzone("|");
909                                 sbt_field[1] = SP_SEPARATOR;
910                                 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
911                                 ++sbt_num_fields;
912                                 LOG_INFO("fixed missing field '|'");
913                         }
914                 }
915                 else if(!have_separator)
916                 {
917                         strcpy(sbt_field_title[sbt_num_fields], "|");
918                         sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
919                         sbt_field[sbt_num_fields] = SP_SEPARATOR;
920                         ++sbt_num_fields;
921                         LOG_INFO("fixed missing field '|'");
922                 }
923                 if(!have_secondary)
924                 {
925                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
926                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
927                         sbt_field[sbt_num_fields] = ps_secondary;
928                         ++sbt_num_fields;
929                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
930                 }
931                 if(!have_primary)
932                 {
933                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
934                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
935                         sbt_field[sbt_num_fields] = ps_primary;
936                         ++sbt_num_fields;
937                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
938                 }
939         }
940
941         sbt_field[sbt_num_fields] = SP_END;
942 }
943
944 string Scoreboard_AddPlayerId(string pl_name, entity pl)
945 {
946         string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
947         string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
948         return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
949 }
950
951 // MOVEUP::
952 vector sbt_field_rgb;
953 string sbt_field_icon0;
954 string sbt_field_icon1;
955 string sbt_field_icon2;
956 vector sbt_field_icon0_rgb;
957 vector sbt_field_icon1_rgb;
958 vector sbt_field_icon2_rgb;
959 string Scoreboard_GetName(entity pl)
960 {
961         if(ready_waiting && pl.ready)
962         {
963                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
964         }
965         else if(!teamplay)
966         {
967                 int f = entcs_GetClientColors(pl.sv_entnum);
968                 {
969                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
970                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
971                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
972                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
973                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
974                 }
975         }
976         return entcs_GetName(pl.sv_entnum);
977 }
978
979 string Scoreboard_GetField(entity pl, PlayerScoreField field)
980 {
981         float tmp, num, denom;
982         int f;
983         string str;
984         sbt_field_rgb = '1 1 1';
985         sbt_field_icon0 = "";
986         sbt_field_icon1 = "";
987         sbt_field_icon2 = "";
988         sbt_field_icon0_rgb = '1 1 1';
989         sbt_field_icon1_rgb = '1 1 1';
990         sbt_field_icon2_rgb = '1 1 1';
991         switch(field)
992         {
993                 case SP_PING:
994                         if (!pl.gotscores)
995                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
996                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
997                         f = pl.ping;
998                         if(f == 0)
999                                 return _("N/A");
1000                         tmp = max(0, min(220, f-80)) / 220;
1001                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1002                         return ftos(f);
1003
1004                 case SP_PL:
1005                         if (!pl.gotscores)
1006                                 return _("N/A");
1007                         f = pl.ping_packetloss;
1008                         tmp = pl.ping_movementloss;
1009                         if(f == 0 && tmp == 0)
1010                                 return "";
1011                         str = ftos(ceil(f * 100));
1012                         if(tmp != 0)
1013                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1014                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1015                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1016                         return str;
1017
1018                 case SP_NAME:
1019                         str = Scoreboard_GetName(pl);
1020                         if (autocvar_hud_panel_scoreboard_playerid)
1021                                 str = Scoreboard_AddPlayerId(str, pl);
1022                         return str;
1023
1024                 case SP_FRAGS:
1025                         f = pl.(scores(SP_KILLS));
1026                         f -= pl.(scores(SP_SUICIDES));
1027                         return ftos(f);
1028
1029                 case SP_KDRATIO:
1030                         num = pl.(scores(SP_KILLS));
1031                         denom = pl.(scores(SP_DEATHS));
1032
1033                         if(denom == 0) {
1034                                 sbt_field_rgb = '0 1 0';
1035                                 str = sprintf("%d", num);
1036                         } else if(num <= 0) {
1037                                 sbt_field_rgb = '1 0 0';
1038                                 str = sprintf("%.1f", num/denom);
1039                         } else
1040                                 str = sprintf("%.1f", num/denom);
1041                         return str;
1042
1043                 case SP_SUM:
1044                         f = pl.(scores(SP_KILLS));
1045                         f -= pl.(scores(SP_DEATHS));
1046
1047                         if(f > 0) {
1048                                 sbt_field_rgb = '0 1 0';
1049                         } else if(f == 0) {
1050                                 sbt_field_rgb = '1 1 1';
1051                         } else {
1052                                 sbt_field_rgb = '1 0 0';
1053                         }
1054                         return ftos(f);
1055
1056                 case SP_ELO:
1057                 {
1058                         float elo = pl.(scores(SP_ELO));
1059                         switch (elo) {
1060                                 case -1: return "...";
1061                                 case -2: return _("N/A");
1062                                 default: return ftos(elo);
1063                         }
1064                 }
1065
1066                 case SP_FPS:
1067                 {
1068                         float fps = pl.(scores(SP_FPS));
1069                         if(fps == 0)
1070                         {
1071                                 sbt_field_rgb = '1 1 1';
1072                                 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1073                         }
1074                         //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1075                         sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1076                         return ftos(fps);
1077                 }
1078
1079                 case SP_DMG: case SP_DMGTAKEN:
1080                         return sprintf("%.1f k", pl.(scores(field)) / 1000);
1081
1082                 default: case SP_SCORE:
1083                         tmp = pl.(scores(field));
1084                         f = scores_flags(field);
1085                         if(field == ps_primary)
1086                                 sbt_field_rgb = '1 1 0';
1087                         else if(field == ps_secondary)
1088                                 sbt_field_rgb = '0 1 1';
1089                         else
1090                                 sbt_field_rgb = '1 1 1';
1091                         return ScoreString(f, tmp);
1092         }
1093         //return "error";
1094 }
1095
1096 float sbt_fixcolumnwidth_len;
1097 float sbt_fixcolumnwidth_iconlen;
1098 float sbt_fixcolumnwidth_marginlen;
1099
1100 string Scoreboard_FixColumnWidth(int i, string str)
1101 {
1102         TC(int, i);
1103         float f;
1104         vector sz;
1105
1106         sbt_fixcolumnwidth_iconlen = 0;
1107
1108         if(sbt_field_icon0 != "")
1109         {
1110                 sz = draw_getimagesize(sbt_field_icon0);
1111                 f = sz.x / sz.y;
1112                 if(sbt_fixcolumnwidth_iconlen < f)
1113                         sbt_fixcolumnwidth_iconlen = f;
1114         }
1115
1116         if(sbt_field_icon1 != "")
1117         {
1118                 sz = draw_getimagesize(sbt_field_icon1);
1119                 f = sz.x / sz.y;
1120                 if(sbt_fixcolumnwidth_iconlen < f)
1121                         sbt_fixcolumnwidth_iconlen = f;
1122         }
1123
1124         if(sbt_field_icon2 != "")
1125         {
1126                 sz = draw_getimagesize(sbt_field_icon2);
1127                 f = sz.x / sz.y;
1128                 if(sbt_fixcolumnwidth_iconlen < f)
1129                         sbt_fixcolumnwidth_iconlen = f;
1130         }
1131
1132         if(sbt_fixcolumnwidth_iconlen != 0)
1133         {
1134                 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1135                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1136         }
1137         else
1138                 sbt_fixcolumnwidth_marginlen = 0;
1139
1140         if(sbt_field[i] == SP_NAME) // name gets all remaining space
1141         {
1142                 int j;
1143                 float remaining_space = 0;
1144                 for(j = 0; j < sbt_num_fields; ++j)
1145                         if(j != i)
1146                                 if (sbt_field[i] != SP_SEPARATOR)
1147                                         remaining_space += sbt_field_size[j] + hud_fontsize.x;
1148                 sbt_field_size[i] = panel_size.x - remaining_space;
1149
1150                 if (sbt_fixcolumnwidth_iconlen != 0)
1151                         remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1152                 float namesize = panel_size.x - remaining_space;
1153                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1154                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1155
1156                 max_namesize = vid_conwidth - remaining_space;
1157         }
1158         else
1159                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1160
1161         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1162         if(sbt_field_size[i] < f)
1163                 sbt_field_size[i] = f;
1164
1165         return str;
1166 }
1167
1168 void Scoreboard_initFieldSizes()
1169 {
1170         for(int i = 0; i < sbt_num_fields; ++i)
1171         {
1172                 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1173                 Scoreboard_FixColumnWidth(i, "");
1174         }
1175 }
1176
1177 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1178 {
1179         int i;
1180         vector column_dim = eY * panel_size.y;
1181         if(other_players)
1182                 column_dim.y -= 1.25 * hud_fontsize.y;
1183         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1184         pos.x += hud_fontsize.x * 0.5;
1185         for(i = 0; i < sbt_num_fields; ++i)
1186         {
1187                 if(sbt_field[i] == SP_SEPARATOR)
1188                         break;
1189                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1190                 if (sbt_highlight)
1191                         if (i % 2)
1192                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1193                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1194                 pos.x += column_dim.x;
1195         }
1196         if(sbt_field[i] == SP_SEPARATOR)
1197         {
1198                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1199                 for(i = sbt_num_fields - 1; i > 0; --i)
1200                 {
1201                         if(sbt_field[i] == SP_SEPARATOR)
1202                                 break;
1203
1204                         pos.x -= sbt_field_size[i];
1205
1206                         if (sbt_highlight)
1207                                 if (!(i % 2))
1208                                 {
1209                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1210                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1211                                 }
1212
1213                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1214                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1215                         pos.x -= hud_fontsize.x;
1216                 }
1217         }
1218
1219         pos.x = panel_pos.x;
1220         pos.y += 1.25 * hud_fontsize.y;
1221         return pos;
1222 }
1223
1224 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1225 {
1226         TC(bool, is_self); TC(int, pl_number);
1227         string str;
1228         bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1229
1230         vector h_pos = item_pos;
1231         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1232         // alternated rows highlighting
1233         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1234         {
1235                 if (pl == scoreboard_selected_player)
1236                         drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1237         }
1238         else if(is_self)
1239                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1240         else if((sbt_highlight) && (!(pl_number % 2)))
1241                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1242
1243         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1244
1245         vector pos = item_pos;
1246         // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1247         if (is_self)
1248                 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1249
1250         pos.x += hud_fontsize.x * 0.5;
1251         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1252         vector tmp = '0 0 0';
1253         int i;
1254         PlayerScoreField field;
1255         for(i = 0; i < sbt_num_fields; ++i)
1256         {
1257                 field = sbt_field[i];
1258                 if(field == SP_SEPARATOR)
1259                         break;
1260
1261                 if(is_spec && field != SP_NAME && field != SP_PING) {
1262                         pos.x += sbt_field_size[i] + hud_fontsize.x;
1263                         continue;
1264                 }
1265                 str = Scoreboard_GetField(pl, field);
1266                 str = Scoreboard_FixColumnWidth(i, str);
1267
1268                 pos.x += sbt_field_size[i] + hud_fontsize.x;
1269
1270                 if(field == SP_NAME) {
1271                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1272                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1273                 } else {
1274                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1275                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1276                 }
1277
1278                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1279                 if(sbt_field_icon0 != "")
1280                         drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1281                 if(sbt_field_icon1 != "")
1282                         drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1283                 if(sbt_field_icon2 != "")
1284                         drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1285         }
1286
1287         if(sbt_field[i] == SP_SEPARATOR)
1288         {
1289                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1290                 for(i = sbt_num_fields-1; i > 0; --i)
1291                 {
1292                         field = sbt_field[i];
1293                         if(field == SP_SEPARATOR)
1294                                 break;
1295
1296                         if(is_spec && field != SP_NAME && field != SP_PING) {
1297                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1298                                 continue;
1299                         }
1300
1301                         str = Scoreboard_GetField(pl, field);
1302                         str = Scoreboard_FixColumnWidth(i, str);
1303
1304                         if(field == SP_NAME) {
1305                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1306                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1307                         } else {
1308                                 tmp.x = sbt_fixcolumnwidth_len;
1309                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1310                         }
1311
1312                         tmp.x = sbt_field_size[i];
1313                         if(sbt_field_icon0 != "")
1314                                 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1315                         if(sbt_field_icon1 != "")
1316                                 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1317                         if(sbt_field_icon2 != "")
1318                                 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1319                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
1320                 }
1321         }
1322
1323         if(pl.eliminated)
1324                 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1325 }
1326
1327 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1328 {
1329         int i = 0;
1330         vector h_pos = item_pos;
1331         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1332
1333         bool complete = (this_team == NUM_SPECTATOR);
1334
1335         if(!complete)
1336         if((sbt_highlight) && (!(pl_number % 2)))
1337                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1338
1339         vector pos = item_pos;
1340         pos.x += hud_fontsize.x * 0.5;
1341         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1342
1343         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1344         if(!complete)
1345                 width_limit -= stringwidth("...", false, hud_fontsize);
1346         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1347         static float max_name_width = 0;
1348         string field = "";
1349         float fieldsize = 0;
1350         float min_fieldsize = 0;
1351         float fieldpadding = hud_fontsize.x * 0.25;
1352         if(this_team == NUM_SPECTATOR)
1353         {
1354                 if(autocvar_hud_panel_scoreboard_spectators_showping)
1355                         min_fieldsize = stringwidth("999", false, hud_fontsize);
1356         }
1357         else if(autocvar_hud_panel_scoreboard_others_showscore)
1358                 min_fieldsize = stringwidth("99", false, hud_fontsize);
1359         for(i = 0; pl; pl = pl.sort_next)
1360         {
1361                 if(pl.team != this_team)
1362                         continue;
1363                 if(pl == ignored_pl)
1364                         continue;
1365
1366                 field = "";
1367                 if(this_team == NUM_SPECTATOR)
1368                 {
1369                         if(autocvar_hud_panel_scoreboard_spectators_showping)
1370                                 field = Scoreboard_GetField(pl, SP_PING);
1371                 }
1372                 else if(autocvar_hud_panel_scoreboard_others_showscore)
1373                         field = Scoreboard_GetField(pl, SP_SCORE);
1374
1375                 string str = entcs_GetName(pl.sv_entnum);
1376                 if (autocvar_hud_panel_scoreboard_playerid)
1377                         str = Scoreboard_AddPlayerId(str, pl);
1378                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1379                 float column_width = stringwidth(str, true, hud_fontsize);
1380                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1381                 {
1382                         if(column_width > max_name_width)
1383                                 max_name_width = column_width;
1384                         column_width = max_name_width;
1385                 }
1386                 if(field != "")
1387                 {
1388                         fieldsize = stringwidth(field, false, hud_fontsize);
1389                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1390                 }
1391
1392                 if(pos.x + column_width > width_limit)
1393                 {
1394                         ++i;
1395                         if(!complete)
1396                         {
1397                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1398                                 break;
1399                         }
1400                         else
1401                         {
1402                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1403                                 pos.y += hud_fontsize.y * 1.25;
1404                         }
1405                 }
1406
1407                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1408                 {
1409                         if (pl == scoreboard_selected_player)
1410                         {
1411                                 h_size.x = column_width + hud_fontsize.x * 0.25;
1412                                 h_size.y = hud_fontsize.y;
1413                                 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1414                         }
1415                 }
1416
1417                 vector name_pos = pos;
1418                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1419                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1420                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1421                 if(field != "")
1422                 {
1423                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1424                         h_size.y = hud_fontsize.y;
1425                         vector field_pos = pos;
1426                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1427                                 field_pos.x += column_width - h_size.x;
1428                         if(sbt_highlight)
1429                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1430                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1431                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1432                 }
1433                 if(pl.eliminated)
1434                 {
1435                         h_size.x = column_width + hud_fontsize.x * 0.25;
1436                         h_size.y = hud_fontsize.y;
1437                         drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1438                 }
1439                 pos.x += column_width;
1440                 pos.x += hud_fontsize.x;
1441         }
1442         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1443 }
1444
1445 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1446 {
1447         int max_players = 999;
1448         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1449         {
1450                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1451                 if(teamplay)
1452                 {
1453                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1454                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1455                         height /= team_count;
1456                 }
1457                 else
1458                         height -= panel_bg_padding * 2; // - padding
1459                 max_players = floor(height / (hud_fontsize.y * 1.25));
1460                 if(max_players <= 1)
1461                         max_players = 1;
1462                 if(max_players == tm.team_size)
1463                         max_players = 999;
1464         }
1465
1466         entity pl;
1467         entity me = playerslots[current_player];
1468         panel_pos = pos;
1469         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1470         panel_size.y += panel_bg_padding * 2;
1471
1472         vector scoreboard_selected_hl_pos = pos;
1473         vector scoreboard_selected_hl_size = '0 0 0';
1474         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1475         scoreboard_selected_hl_size.y = panel_size.y;
1476
1477         HUD_Panel_DrawBg();
1478
1479         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1480         if(panel.current_panel_bg != "0")
1481                 end_pos.y += panel_bg_border * 2;
1482
1483         if(panel_bg_padding)
1484         {
1485                 panel_pos += '1 1 0' * panel_bg_padding;
1486                 panel_size -= '2 2 0' * panel_bg_padding;
1487         }
1488
1489         pos = panel_pos;
1490         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1491
1492         // rounded header
1493         if (sbt_bg_alpha)
1494                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1495
1496         pos.y += 1.25 * hud_fontsize.y;
1497
1498         // table background
1499         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1500         if (sbt_bg_alpha)
1501                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1502
1503
1504         // print header row and highlight columns
1505         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1506
1507         // fill the table and draw the rows
1508         bool is_self = false;
1509         bool self_shown = false;
1510         int i = 0;
1511         for(pl = players.sort_next; pl; pl = pl.sort_next)
1512         {
1513                 if(pl.team != tm.team)
1514                         continue;
1515                 if(i == max_players - 2 && pl != me)
1516                 {
1517                         if(!self_shown && me.team == tm.team)
1518                         {
1519                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1520                                 self_shown = true;
1521                                 pos.y += 1.25 * hud_fontsize.y;
1522                                 ++i;
1523                         }
1524                 }
1525                 if(i >= max_players - 1)
1526                 {
1527                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1528                         break;
1529                 }
1530                 is_self = (pl.sv_entnum == current_player);
1531                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1532                 if(is_self)
1533                         self_shown = true;
1534                 pos.y += 1.25 * hud_fontsize.y;
1535                 ++i;
1536         }
1537
1538         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1539         {
1540                 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1541                 {
1542                         float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1543                         _alpha *= panel_fg_alpha;
1544                         if (_alpha)
1545                                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1546                 }
1547         }
1548
1549         panel_size.x += panel_bg_padding * 2; // restore initial width
1550         return end_pos;
1551 }
1552
1553 bool Scoreboard_WouldDraw()
1554 {
1555         if (scoreboard_ui_enabled)
1556                 return true;
1557         else if (MUTATOR_CALLHOOK(DrawScoreboard))
1558                 return false;
1559         else if (QuickMenu_IsOpened())
1560                 return false;
1561         else if (HUD_Radar_Clickable())
1562                 return false;
1563         else if (scoreboard_showscores)
1564                 return true;
1565         else if (intermission == 1)
1566                 return true;
1567         else if (intermission == 2)
1568                 return false;
1569         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1570                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1571         {
1572                 return true;
1573         }
1574         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1575                 return true;
1576         return false;
1577 }
1578
1579 float average_accuracy;
1580 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1581 {
1582         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1583
1584         WepSet weapons_stat = WepSet_GetFromStat();
1585         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1586         int disownedcnt = 0;
1587         int nHidden = 0;
1588         FOREACH(Weapons, it != WEP_Null, {
1589                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1590
1591                 WepSet set = it.m_wepset;
1592                 if(it.spawnflags & WEP_TYPE_OTHER)
1593                 {
1594                         ++nHidden;
1595                         continue;
1596                 }
1597                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1598                 {
1599                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1600                                 ++nHidden;
1601                         else
1602                                 ++disownedcnt;
1603                 }
1604         });
1605
1606         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1607         if (weapon_cnt <= 0) return pos;
1608
1609         int rows = 1;
1610         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1611                 rows = 2;
1612         int columns = ceil(weapon_cnt / rows);
1613
1614         float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1615         float weapon_height = hud_fontsize.y * 2.3 / aspect;
1616         float height = weapon_height + hud_fontsize.y;
1617
1618         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);
1619         pos.y += 1.25 * hud_fontsize.y;
1620         if(panel.current_panel_bg != "0")
1621                 pos.y += panel_bg_border;
1622
1623         panel_pos = pos;
1624         panel_size.y = height * rows;
1625         panel_size.y += panel_bg_padding * 2;
1626
1627         float panel_bg_alpha_save = panel_bg_alpha;
1628         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1629         HUD_Panel_DrawBg();
1630         panel_bg_alpha = panel_bg_alpha_save;
1631
1632         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1633         if(panel.current_panel_bg != "0")
1634                 end_pos.y += panel_bg_border * 2;
1635
1636         if(panel_bg_padding)
1637         {
1638                 panel_pos += '1 1 0' * panel_bg_padding;
1639                 panel_size -= '2 2 0' * panel_bg_padding;
1640         }
1641
1642         pos = panel_pos;
1643         vector tmp = panel_size;
1644
1645         float weapon_width = tmp.x / columns / rows;
1646
1647         if (sbt_bg_alpha)
1648                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1649
1650         if(sbt_highlight)
1651         {
1652                 // column highlighting
1653                 for (int i = 0; i < columns; ++i)
1654                         if ((i % 2) == 0)
1655                                 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);
1656
1657                 // row highlighting
1658                 for (int i = 0; i < rows; ++i)
1659                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1660         }
1661
1662         average_accuracy = 0;
1663         int weapons_with_stats = 0;
1664         if (rows == 2)
1665                 pos.x += weapon_width / 2;
1666
1667         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1668                 rgb = '1 1 1';
1669         else
1670                 Accuracy_LoadColors();
1671
1672         float oldposx = pos.x;
1673         vector tmpos = pos;
1674
1675         int column = 0;
1676         FOREACH(Weapons, it != WEP_Null, {
1677                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1678
1679                 WepSet set = it.m_wepset;
1680                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1681                         continue;
1682                 if (it.spawnflags & WEP_TYPE_OTHER)
1683                         continue;
1684
1685                 float weapon_alpha;
1686                 if (weapon_stats >= 0)
1687                         weapon_alpha = sbt_fg_alpha;
1688                 else
1689                         weapon_alpha = 0.2 * sbt_fg_alpha;
1690
1691                 // weapon icon
1692                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1693                 // the accuracy
1694                 if (weapon_stats >= 0) {
1695                         weapons_with_stats += 1;
1696                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1697
1698                         string s = sprintf("%d%%", weapon_stats * 100);
1699                         float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1700
1701                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1702                                 rgb = Accuracy_GetColor(weapon_stats);
1703
1704                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1705                 }
1706                 tmpos.x += weapon_width * rows;
1707                 pos.x += weapon_width * rows;
1708                 if (rows == 2 && column == columns - 1) {
1709                         tmpos.x = oldposx;
1710                         tmpos.y += height;
1711                         pos.y += height;
1712                 }
1713                 ++column;
1714         });
1715
1716         if (weapons_with_stats)
1717                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1718
1719         panel_size.x += panel_bg_padding * 2; // restore initial width
1720
1721         return end_pos;
1722 }
1723
1724 bool is_item_filtered(entity it)
1725 {
1726         if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1727                 return false;
1728         int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1729         if (mask <= 0)
1730                 return false;
1731         if (it.instanceOfArmor || it.instanceOfHealth)
1732         {
1733                 int ha_mask = floor(mask) % 10;
1734                 switch (ha_mask)
1735                 {
1736                         default: return false;
1737                         case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1738                         case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1739                         case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1740                         case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1741                 }
1742         }
1743         if (it.instanceOfAmmo)
1744         {
1745                 int ammo_mask = floor(mask / 10) % 10;
1746                 return (ammo_mask == 1);
1747         }
1748         return false;
1749 }
1750
1751 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1752 {
1753         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1754
1755         int disowned_cnt = 0;
1756         int uninteresting_cnt = 0;
1757         IL_EACH(default_order_items, true, {
1758                 int q = g_inventory.inv_items[it.m_id];
1759                 //q = 1; // debug: display all items
1760                 if (is_item_filtered(it))
1761                         ++uninteresting_cnt;
1762                 else if (!q)
1763                         ++disowned_cnt;
1764         });
1765         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1766         int n = items_cnt - disowned_cnt;
1767         if (n <= 0) return pos;
1768
1769         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1770         int columns = max(6, ceil(n / rows));
1771
1772         float item_height = hud_fontsize.y * 2.3;
1773         float height = item_height + hud_fontsize.y;
1774
1775         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1776         pos.y += 1.25 * hud_fontsize.y;
1777         if(panel.current_panel_bg != "0")
1778                 pos.y += panel_bg_border;
1779
1780         panel_pos = pos;
1781         panel_size.y = height * rows;
1782         panel_size.y += panel_bg_padding * 2;
1783
1784         float panel_bg_alpha_save = panel_bg_alpha;
1785         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1786         HUD_Panel_DrawBg();
1787         panel_bg_alpha = panel_bg_alpha_save;
1788
1789         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1790         if(panel.current_panel_bg != "0")
1791                 end_pos.y += panel_bg_border * 2;
1792
1793         if(panel_bg_padding)
1794         {
1795                 panel_pos += '1 1 0' * panel_bg_padding;
1796                 panel_size -= '2 2 0' * panel_bg_padding;
1797         }
1798
1799         pos = panel_pos;
1800         vector tmp = panel_size;
1801
1802         float item_width = tmp.x / columns / rows;
1803
1804         if (sbt_bg_alpha)
1805                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1806
1807         if(sbt_highlight)
1808         {
1809                 // column highlighting
1810                 for (int i = 0; i < columns; ++i)
1811                         if ((i % 2) == 0)
1812                                 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);
1813
1814                 // row highlighting
1815                 for (int i = 0; i < rows; ++i)
1816                         drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1817         }
1818
1819         if (rows == 2)
1820                 pos.x += item_width / 2;
1821
1822         float oldposx = pos.x;
1823         vector tmpos = pos;
1824
1825         int column = 0;
1826         IL_EACH(default_order_items, !is_item_filtered(it), {
1827                 int n = g_inventory.inv_items[it.m_id];
1828                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1829                 if (n <= 0) continue;
1830                 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);
1831                 string s = ftos(n);
1832                 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1833                 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1834                 tmpos.x += item_width * rows;
1835                 pos.x += item_width * rows;
1836                 if (rows == 2 && column == columns - 1) {
1837                         tmpos.x = oldposx;
1838                         tmpos.y += height;
1839                         pos.y += height;
1840                 }
1841                 ++column;
1842         });
1843
1844         panel_size.x += panel_bg_padding * 2; // restore initial width
1845
1846         return end_pos;
1847 }
1848
1849 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1850         float px = pos.x;
1851         pos.x += hud_fontsize.x * 0.25;
1852         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1853         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1854         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1855         pos.x = px;
1856         pos.y += hud_fontsize.y;
1857
1858         return pos;
1859 }
1860
1861 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1862         float stat_secrets_found, stat_secrets_total;
1863         float stat_monsters_killed, stat_monsters_total;
1864         float rows = 0;
1865         string val;
1866
1867         // get monster stats
1868         stat_monsters_killed = STAT(MONSTERS_KILLED);
1869         stat_monsters_total = STAT(MONSTERS_TOTAL);
1870
1871         // get secrets stats
1872         stat_secrets_found = STAT(SECRETS_FOUND);
1873         stat_secrets_total = STAT(SECRETS_TOTAL);
1874
1875         // get number of rows
1876         if(stat_secrets_total)
1877                 rows += 1;
1878         if(stat_monsters_total)
1879                 rows += 1;
1880
1881         // if no rows, return
1882         if (!rows)
1883                 return pos;
1884
1885         //  draw table header
1886         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1887         pos.y += 1.25 * hud_fontsize.y;
1888         if(panel.current_panel_bg != "0")
1889                 pos.y += panel_bg_border;
1890
1891         panel_pos = pos;
1892         panel_size.y = hud_fontsize.y * rows;
1893         panel_size.y += panel_bg_padding * 2;
1894         HUD_Panel_DrawBg();
1895
1896         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1897         if(panel.current_panel_bg != "0")
1898                 end_pos.y += panel_bg_border * 2;
1899
1900         if(panel_bg_padding)
1901         {
1902                 panel_pos += '1 1 0' * panel_bg_padding;
1903                 panel_size -= '2 2 0' * panel_bg_padding;
1904         }
1905
1906         pos = panel_pos;
1907         vector tmp = panel_size;
1908
1909         if (sbt_bg_alpha)
1910                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1911
1912         // draw monsters
1913         if(stat_monsters_total)
1914         {
1915                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1916                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1917         }
1918
1919         // draw secrets
1920         if(stat_secrets_total)
1921         {
1922                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1923                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1924         }
1925
1926         panel_size.x += panel_bg_padding * 2; // restore initial width
1927         return end_pos;
1928 }
1929
1930 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1931 {
1932         int i;
1933         RANKINGS_RECEIVED_CNT = 0;
1934         for (i=RANKINGS_CNT-1; i>=0; --i)
1935                 if (grecordtime[i])
1936                         ++RANKINGS_RECEIVED_CNT;
1937
1938         if (RANKINGS_RECEIVED_CNT == 0)
1939                 return pos;
1940
1941         vector hl_rgb = rgb + '0.5 0.5 0.5';
1942
1943         vector scoreboard_selected_hl_pos = pos;
1944
1945         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1946         pos.y += 1.25 * hud_fontsize.y;
1947         if(panel.current_panel_bg != "0")
1948                 pos.y += panel_bg_border;
1949
1950         vector scoreboard_selected_hl_size = '0 0 0';
1951         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1952         scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1953
1954         panel_pos = pos;
1955
1956         float namesize = 0;
1957         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1958         {
1959                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1960                 if(f > namesize)
1961                         namesize = f;
1962         }
1963         bool cut = false;
1964         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1965         {
1966                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1967                 cut = true;
1968         }
1969
1970         float ranksize = 3 * hud_fontsize.x;
1971         float timesize = 5 * hud_fontsize.x;
1972         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1973         rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1974         rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1975         if (!rankings_cnt)
1976         {
1977                 rankings_cnt = RANKINGS_RECEIVED_CNT;
1978                 rankings_rows = ceil(rankings_cnt / rankings_columns);
1979         }
1980
1981         // expand name column to fill the entire row
1982         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1983         namesize += available_space;
1984         columnsize.x += available_space;
1985
1986         panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1987         panel_size.y += panel_bg_padding * 2;
1988         scoreboard_selected_hl_size.y += panel_size.y;
1989
1990         HUD_Panel_DrawBg();
1991
1992         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1993         if(panel.current_panel_bg != "0")
1994                 end_pos.y += panel_bg_border * 2;
1995
1996         if(panel_bg_padding)
1997         {
1998                 panel_pos += '1 1 0' * panel_bg_padding;
1999                 panel_size -= '2 2 0' * panel_bg_padding;
2000         }
2001
2002         pos = panel_pos;
2003
2004         if (sbt_bg_alpha)
2005                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2006
2007         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2008         string str = "";
2009         int column = 0, j = 0;
2010         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2011         int start_item = rankings_start_column * rankings_rows;
2012         for(i = start_item; i < start_item + rankings_cnt; ++i)
2013         {
2014                 int t = grecordtime[i];
2015                 if (t == 0)
2016                         continue;
2017
2018                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2019                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2020                 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2021                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2022
2023                 str = count_ordinal(i+1);
2024                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2025                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2026                 str = ColorTranslateRGB(grecordholder[i]);
2027                 if(cut)
2028                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2029                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2030
2031                 pos.y += 1.25 * hud_fontsize.y;
2032                 j++;
2033                 if(j >= rankings_rows)
2034                 {
2035                         column++;
2036                         j = 0;
2037                         pos.x += panel_size.x / rankings_columns;
2038                         pos.y = panel_pos.y;
2039                 }
2040         }
2041         strfree(zoned_name_self);
2042
2043         if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2044         {
2045                 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2046                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2047         }
2048
2049         panel_size.x += panel_bg_padding * 2; // restore initial width
2050         return end_pos;
2051 }
2052
2053 bool have_weapon_stats;
2054 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2055 {
2056         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2057                 return false;
2058         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2059                 return false;
2060
2061         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2062                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2063                 && !intermission)
2064         {
2065                 return false;
2066         }
2067
2068         if (!have_weapon_stats)
2069         {
2070                 FOREACH(Weapons, it != WEP_Null, {
2071                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2072                         if (weapon_stats >= 0)
2073                         {
2074                                 have_weapon_stats = true;
2075                                 break;
2076                         }
2077                 });
2078                 if (!have_weapon_stats)
2079                         return false;
2080         }
2081
2082         return true;
2083 }
2084
2085 bool have_item_stats;
2086 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2087 {
2088         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2089                 return false;
2090         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2091                 return false;
2092
2093         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2094                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2095                 && !intermission)
2096         {
2097                 return false;
2098         }
2099
2100         if (!have_item_stats)
2101         {
2102                 IL_EACH(default_order_items, true, {
2103                         if (!is_item_filtered(it))
2104                         {
2105                                 int q = g_inventory.inv_items[it.m_id];
2106                                 //q = 1; // debug: display all items
2107                                 if (q)
2108                                 {
2109                                         have_item_stats = true;
2110                                         break;
2111                                 }
2112                         }
2113                 });
2114                 if (!have_item_stats)
2115                         return false;
2116         }
2117
2118         return true;
2119 }
2120
2121 vector Scoreboard_Spectators_Draw(vector pos) {
2122
2123         entity pl, tm;
2124         string str = "";
2125
2126         for(pl = players.sort_next; pl; pl = pl.sort_next)
2127         {
2128                 if(pl.team == NUM_SPECTATOR)
2129                 {
2130                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2131                                 if(tm.team == NUM_SPECTATOR)
2132                                         break;
2133                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2134                         draw_beginBoldFont();
2135                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2136                         draw_endBoldFont();
2137                         pos.y += 1.25 * hud_fontsize.y;
2138
2139                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2140                         pos.y += 1.25 * hud_fontsize.y;
2141
2142                         break;
2143                 }
2144         }
2145         if (str != "") // if there's at least one spectator
2146                 pos.y += 0.5 * hud_fontsize.y;
2147
2148         return pos;
2149 }
2150
2151 void Scoreboard_Draw()
2152 {
2153         if(!autocvar__hud_configure)
2154         {
2155                 if(!hud_draw_maximized) return;
2156
2157                 // frametime checks allow to toggle the scoreboard even when the game is paused
2158                 if(scoreboard_active) {
2159                         if (scoreboard_fade_alpha == 0)
2160                                 scoreboard_time = time;
2161                         if(hud_configure_menu_open == 1)
2162                                 scoreboard_fade_alpha = 1;
2163                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2164                         if (scoreboard_fadeinspeed && frametime)
2165                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2166                         else
2167                                 scoreboard_fade_alpha = 1;
2168                         if(hud_fontsize_str != autocvar_hud_fontsize)
2169                         {
2170                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2171                                 Scoreboard_initFieldSizes();
2172                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2173                         }
2174                 }
2175                 else {
2176                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2177                         if (scoreboard_fadeoutspeed && frametime)
2178                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2179                         else
2180                                 scoreboard_fade_alpha = 0;
2181                 }
2182
2183                 if (!scoreboard_fade_alpha)
2184                 {
2185                         scoreboard_acc_fade_alpha = 0;
2186                         scoreboard_itemstats_fade_alpha = 0;
2187                         return;
2188                 }
2189         }
2190         else
2191                 scoreboard_fade_alpha = 0;
2192
2193         if (autocvar_hud_panel_scoreboard_dynamichud)
2194                 HUD_Scale_Enable();
2195         else
2196                 HUD_Scale_Disable();
2197
2198         if(scoreboard_fade_alpha <= 0)
2199                 return;
2200         panel_fade_alpha *= scoreboard_fade_alpha;
2201         HUD_Panel_LoadCvars();
2202
2203         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2204         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2205         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2206         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2207         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2208         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2209         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2210
2211         // don't overlap with con_notify
2212         if(!autocvar__hud_configure)
2213                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2214
2215         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2216         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2217         scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2218         scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2219         panel_pos.x = scoreboard_left;
2220         panel_size.x = fixed_scoreboard_width;
2221
2222         Scoreboard_UpdatePlayerTeams();
2223
2224         scoreboard_top = panel_pos.y;
2225         vector pos = panel_pos;
2226         entity tm;
2227         string str;
2228         vector str_pos;
2229
2230         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2231
2232         // Begin of Game Info Section
2233         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2234         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2235
2236         // Game Info: Game Type
2237         if (scoreboard_ui_enabled == 2)
2238                 str = _("Team Selection");
2239         else
2240                 str = MapInfo_Type_ToText(gametype);
2241         draw_beginBoldFont();
2242         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);
2243         draw_endBoldFont();
2244
2245         pos.y += sb_gameinfo_type_fontsize.y;
2246         // Game Info: Game Detail
2247         if (scoreboard_ui_enabled == 2)
2248         {
2249                 if (scoreboard_selected_team)
2250                         str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2251                 else
2252                         str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2253                 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);
2254
2255                 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2256                 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2257                 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);
2258         }
2259         else
2260         {
2261                 float tl = STAT(TIMELIMIT);
2262                 float fl = STAT(FRAGLIMIT);
2263                 float ll = STAT(LEADLIMIT);
2264                 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2265                 str = "";
2266                 if(tl > 0)
2267                         str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2268                 if(!gametype.m_hidelimits)
2269                 {
2270                         if(fl > 0)
2271                         {
2272                                 if(tl > 0)
2273                                         str = strcat(str, "^7 / "); // delimiter
2274                                 if(teamplay)
2275                                 {
2276                                         str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2277                                                 (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
2278                                                 (teamscores_label(ts_primary) == "fastest") ? "" :
2279                                                 TranslateScoresLabel(teamscores_label(ts_primary))));
2280                                 }
2281                                 else
2282                                 {
2283                                         str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2284                                                 (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
2285                                                 (scores_label(ps_primary) == "fastest") ? "" :
2286                                                 TranslateScoresLabel(scores_label(ps_primary))));
2287                                 }
2288                         }
2289                         if(ll > 0)
2290                         {
2291                                 if(tl > 0 || fl > 0)
2292                                 {
2293                                         // delimiter
2294                                         if (ll_and_fl && fl > 0)
2295                                                 str = strcat(str, "^7 & ");
2296                                         else
2297                                                 str = strcat(str, "^7 / ");
2298                                 }
2299
2300                                 if(teamplay)
2301                                 {
2302                                         str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2303                                                 (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
2304                                                 (teamscores_label(ts_primary) == "fastest") ? "" :
2305                                                 TranslateScoresLabel(teamscores_label(ts_primary))));
2306                                 }
2307                                 else
2308                                 {
2309                                         str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2310                                                 (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
2311                                                 (scores_label(ps_primary) == "fastest") ? "" :
2312                                                 TranslateScoresLabel(scores_label(ps_primary))));
2313                                 }
2314                         }
2315                 }
2316                 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
2317                 // map name
2318                 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2319                 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2320         }
2321         // End of Game Info Section
2322
2323         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2324         if(panel.current_panel_bg != "0")
2325                 pos.y += panel_bg_border;
2326
2327         // Draw the scoreboard
2328         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2329         if(scale <= 0)
2330                 scale = 0.25;
2331         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2332
2333         if(teamplay)
2334         {
2335                 vector panel_bg_color_save = panel_bg_color;
2336                 vector team_score_baseoffset;
2337                 vector team_size_baseoffset;
2338                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2339                 {
2340                         // put team score to the left of scoreboard (and team size to the right)
2341                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2342                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2343                         if(panel.current_panel_bg != "0")
2344                         {
2345                                 team_score_baseoffset.x -= panel_bg_border;
2346                                 team_size_baseoffset.x += panel_bg_border;
2347                         }
2348                 }
2349                 else
2350                 {
2351                         // put team score to the right of scoreboard (and team size to the left)
2352                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2353                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2354                         if(panel.current_panel_bg != "0")
2355                         {
2356                                 team_score_baseoffset.x += panel_bg_border;
2357                                 team_size_baseoffset.x -= panel_bg_border;
2358                         }
2359                 }
2360
2361                 int team_size_total = 0;
2362                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2363                 {
2364                         // calculate team size total (sum of all team sizes)
2365                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2366                                 if(tm.team != NUM_SPECTATOR)
2367                                         team_size_total += tm.team_size;
2368                 }
2369
2370                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2371                 {
2372                         if(tm.team == NUM_SPECTATOR)
2373                                 continue;
2374                         if(!tm.team)
2375                                 continue;
2376
2377                         draw_beginBoldFont();
2378                         vector rgb = Team_ColorRGB(tm.team);
2379                         str = ftos(tm.(teamscores(ts_primary)));
2380                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2381                         {
2382                                 // team score on the left (default)
2383                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2384                         }
2385                         else
2386                         {
2387                                 // team score on the right
2388                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2389                         }
2390                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2391
2392                         // team size (if set to show on the side)
2393                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2394                         {
2395                                 // calculate the starting position for the whole team size info string
2396                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
2397                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2398                                 {
2399                                         // team size on the left
2400                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2401                                 }
2402                                 else
2403                                 {
2404                                         // team size on the right
2405                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2406                                 }
2407                                 str = sprintf("%d", tm.team_size);
2408                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2409                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2410                                 str = sprintf("/%d", team_size_total);
2411                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2412                         }
2413
2414
2415                         // secondary score, e.g. keyhunt
2416                         if(ts_primary != ts_secondary)
2417                         {
2418                                 str = ftos(tm.(teamscores(ts_secondary)));
2419                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2420                                 {
2421                                         // left
2422                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2423                                 }
2424                                 else
2425                                 {
2426                                         // right
2427                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2428                                 }
2429
2430                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2431                         }
2432                         draw_endBoldFont();
2433                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2434                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2435                         else if(panel_bg_color_team > 0)
2436                                 panel_bg_color = rgb * panel_bg_color_team;
2437                         else
2438                                 panel_bg_color = rgb;
2439                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2440                 }
2441                 panel_bg_color = panel_bg_color_save;
2442         }
2443         else
2444         {
2445                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2446                         if(tm.team != NUM_SPECTATOR)
2447                                 break;
2448
2449                 // display it anyway
2450                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2451         }
2452
2453         // draw scoreboard spectators before accuracy and item stats
2454         if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2455                 pos = Scoreboard_Spectators_Draw(pos);
2456         }
2457
2458         // draw accuracy and item stats
2459         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2460                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2461         if (Scoreboard_ItemStats_WouldDraw(pos.y))
2462                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2463
2464         // draw scoreboard spectators after accuracy and item stats and before rankings
2465         if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2466                 pos = Scoreboard_Spectators_Draw(pos);
2467         }
2468
2469         if(MUTATOR_CALLHOOK(ShowRankings)) {
2470                 string ranktitle = M_ARGV(0, string);
2471                 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2472                 if(race_speedaward_alltimebest)
2473                 {
2474                         string name;
2475                         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2476                         str = "";
2477                         if(race_speedaward)
2478                         {
2479                                 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2480                                 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2481                                 str = strcat(str, " / ");
2482                         }
2483                         name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2484                         str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2485                         drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2486                         pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2487                 }
2488                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2489         }
2490         else
2491                 rankings_cnt = 0;
2492
2493         // draw scoreboard spectators after rankings
2494         if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2495                 pos = Scoreboard_Spectators_Draw(pos);
2496         }
2497
2498         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2499
2500         // draw scoreboard spectators after mapstats
2501         if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2502                 pos = Scoreboard_Spectators_Draw(pos);
2503         }
2504
2505
2506         // print information about respawn status
2507         float respawn_time = STAT(RESPAWN_TIME);
2508         if(!intermission)
2509         if(respawn_time)
2510         {
2511                 if(respawn_time < 0)
2512                 {
2513                         // a negative number means we are awaiting respawn, time value is still the same
2514                         respawn_time *= -1; // remove mark now that we checked it
2515
2516                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2517                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2518                         else
2519                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2520                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2521                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2522                                                 :
2523                                                 count_seconds(ceil(respawn_time - time))
2524                                         )
2525                                 );
2526                 }
2527                 else if(time < respawn_time)
2528                 {
2529                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2530                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2531                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2532                                         :
2533                                         count_seconds(ceil(respawn_time - time))
2534                                 )
2535                         );
2536                 }
2537                 else if(time >= respawn_time)
2538                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2539
2540                 pos.y += 1.2 * hud_fontsize.y;
2541                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2542         }
2543
2544         pos.y += hud_fontsize.y;
2545         if (scoreboard_fade_alpha < 1)
2546                 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2547         else if (pos.y != scoreboard_bottom)
2548         {
2549                 if (pos.y > scoreboard_bottom)
2550                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2551                 else
2552                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2553         }
2554
2555         if (rankings_cnt)
2556         {
2557                 if (scoreboard_fade_alpha == 1)
2558                 {
2559                         if (scoreboard_bottom > 0.95 * vid_conheight)
2560                                 rankings_rows = max(1, rankings_rows - 1);
2561                         else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2562                                 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2563                 }
2564                 rankings_cnt = rankings_rows * rankings_columns;
2565         }
2566 }