Forbid opening scoreboard team selection when the game is over
[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 || intermission)
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         {
1557                 if (intermission && scoreboard_ui_enabled == 2)
1558                 {
1559                         HUD_Scoreboard_UI_Disable();
1560                         return false;
1561                 }
1562                 return true;
1563         }
1564         else if (MUTATOR_CALLHOOK(DrawScoreboard))
1565                 return false;
1566         else if (QuickMenu_IsOpened())
1567                 return false;
1568         else if (HUD_Radar_Clickable())
1569                 return false;
1570         else if (scoreboard_showscores)
1571                 return true;
1572         else if (intermission == 1)
1573                 return true;
1574         else if (intermission == 2)
1575                 return false;
1576         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1577                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1578         {
1579                 return true;
1580         }
1581         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1582                 return true;
1583         return false;
1584 }
1585
1586 float average_accuracy;
1587 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1588 {
1589         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1590
1591         WepSet weapons_stat = WepSet_GetFromStat();
1592         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1593         int disownedcnt = 0;
1594         int nHidden = 0;
1595         FOREACH(Weapons, it != WEP_Null, {
1596                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1597
1598                 WepSet set = it.m_wepset;
1599                 if(it.spawnflags & WEP_TYPE_OTHER)
1600                 {
1601                         ++nHidden;
1602                         continue;
1603                 }
1604                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1605                 {
1606                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1607                                 ++nHidden;
1608                         else
1609                                 ++disownedcnt;
1610                 }
1611         });
1612
1613         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1614         if (weapon_cnt <= 0) return pos;
1615
1616         int rows = 1;
1617         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1618                 rows = 2;
1619         int columns = ceil(weapon_cnt / rows);
1620
1621         float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1622         float weapon_height = hud_fontsize.y * 2.3 / aspect;
1623         float height = weapon_height + hud_fontsize.y;
1624
1625         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);
1626         pos.y += 1.25 * hud_fontsize.y;
1627         if(panel.current_panel_bg != "0")
1628                 pos.y += panel_bg_border;
1629
1630         panel_pos = pos;
1631         panel_size.y = height * rows;
1632         panel_size.y += panel_bg_padding * 2;
1633
1634         float panel_bg_alpha_save = panel_bg_alpha;
1635         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1636         HUD_Panel_DrawBg();
1637         panel_bg_alpha = panel_bg_alpha_save;
1638
1639         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1640         if(panel.current_panel_bg != "0")
1641                 end_pos.y += panel_bg_border * 2;
1642
1643         if(panel_bg_padding)
1644         {
1645                 panel_pos += '1 1 0' * panel_bg_padding;
1646                 panel_size -= '2 2 0' * panel_bg_padding;
1647         }
1648
1649         pos = panel_pos;
1650         vector tmp = panel_size;
1651
1652         float weapon_width = tmp.x / columns / rows;
1653
1654         if (sbt_bg_alpha)
1655                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1656
1657         if(sbt_highlight)
1658         {
1659                 // column highlighting
1660                 for (int i = 0; i < columns; ++i)
1661                         if ((i % 2) == 0)
1662                                 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);
1663
1664                 // row highlighting
1665                 for (int i = 0; i < rows; ++i)
1666                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1667         }
1668
1669         average_accuracy = 0;
1670         int weapons_with_stats = 0;
1671         if (rows == 2)
1672                 pos.x += weapon_width / 2;
1673
1674         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1675                 rgb = '1 1 1';
1676         else
1677                 Accuracy_LoadColors();
1678
1679         float oldposx = pos.x;
1680         vector tmpos = pos;
1681
1682         int column = 0;
1683         FOREACH(Weapons, it != WEP_Null, {
1684                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1685
1686                 WepSet set = it.m_wepset;
1687                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1688                         continue;
1689                 if (it.spawnflags & WEP_TYPE_OTHER)
1690                         continue;
1691
1692                 float weapon_alpha;
1693                 if (weapon_stats >= 0)
1694                         weapon_alpha = sbt_fg_alpha;
1695                 else
1696                         weapon_alpha = 0.2 * sbt_fg_alpha;
1697
1698                 // weapon icon
1699                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1700                 // the accuracy
1701                 if (weapon_stats >= 0) {
1702                         weapons_with_stats += 1;
1703                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1704
1705                         string s = sprintf("%d%%", weapon_stats * 100);
1706                         float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1707
1708                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1709                                 rgb = Accuracy_GetColor(weapon_stats);
1710
1711                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1712                 }
1713                 tmpos.x += weapon_width * rows;
1714                 pos.x += weapon_width * rows;
1715                 if (rows == 2 && column == columns - 1) {
1716                         tmpos.x = oldposx;
1717                         tmpos.y += height;
1718                         pos.y += height;
1719                 }
1720                 ++column;
1721         });
1722
1723         if (weapons_with_stats)
1724                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1725
1726         panel_size.x += panel_bg_padding * 2; // restore initial width
1727
1728         return end_pos;
1729 }
1730
1731 bool is_item_filtered(entity it)
1732 {
1733         if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1734                 return false;
1735         int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1736         if (mask <= 0)
1737                 return false;
1738         if (it.instanceOfArmor || it.instanceOfHealth)
1739         {
1740                 int ha_mask = floor(mask) % 10;
1741                 switch (ha_mask)
1742                 {
1743                         default: return false;
1744                         case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1745                         case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1746                         case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1747                         case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1748                 }
1749         }
1750         if (it.instanceOfAmmo)
1751         {
1752                 int ammo_mask = floor(mask / 10) % 10;
1753                 return (ammo_mask == 1);
1754         }
1755         return false;
1756 }
1757
1758 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1759 {
1760         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1761
1762         int disowned_cnt = 0;
1763         int uninteresting_cnt = 0;
1764         IL_EACH(default_order_items, true, {
1765                 int q = g_inventory.inv_items[it.m_id];
1766                 //q = 1; // debug: display all items
1767                 if (is_item_filtered(it))
1768                         ++uninteresting_cnt;
1769                 else if (!q)
1770                         ++disowned_cnt;
1771         });
1772         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1773         int n = items_cnt - disowned_cnt;
1774         if (n <= 0) return pos;
1775
1776         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1777         int columns = max(6, ceil(n / rows));
1778
1779         float item_height = hud_fontsize.y * 2.3;
1780         float height = item_height + hud_fontsize.y;
1781
1782         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1783         pos.y += 1.25 * hud_fontsize.y;
1784         if(panel.current_panel_bg != "0")
1785                 pos.y += panel_bg_border;
1786
1787         panel_pos = pos;
1788         panel_size.y = height * rows;
1789         panel_size.y += panel_bg_padding * 2;
1790
1791         float panel_bg_alpha_save = panel_bg_alpha;
1792         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1793         HUD_Panel_DrawBg();
1794         panel_bg_alpha = panel_bg_alpha_save;
1795
1796         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1797         if(panel.current_panel_bg != "0")
1798                 end_pos.y += panel_bg_border * 2;
1799
1800         if(panel_bg_padding)
1801         {
1802                 panel_pos += '1 1 0' * panel_bg_padding;
1803                 panel_size -= '2 2 0' * panel_bg_padding;
1804         }
1805
1806         pos = panel_pos;
1807         vector tmp = panel_size;
1808
1809         float item_width = tmp.x / columns / rows;
1810
1811         if (sbt_bg_alpha)
1812                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1813
1814         if(sbt_highlight)
1815         {
1816                 // column highlighting
1817                 for (int i = 0; i < columns; ++i)
1818                         if ((i % 2) == 0)
1819                                 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);
1820
1821                 // row highlighting
1822                 for (int i = 0; i < rows; ++i)
1823                         drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1824         }
1825
1826         if (rows == 2)
1827                 pos.x += item_width / 2;
1828
1829         float oldposx = pos.x;
1830         vector tmpos = pos;
1831
1832         int column = 0;
1833         IL_EACH(default_order_items, !is_item_filtered(it), {
1834                 int n = g_inventory.inv_items[it.m_id];
1835                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1836                 if (n <= 0) continue;
1837                 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);
1838                 string s = ftos(n);
1839                 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1840                 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1841                 tmpos.x += item_width * rows;
1842                 pos.x += item_width * rows;
1843                 if (rows == 2 && column == columns - 1) {
1844                         tmpos.x = oldposx;
1845                         tmpos.y += height;
1846                         pos.y += height;
1847                 }
1848                 ++column;
1849         });
1850
1851         panel_size.x += panel_bg_padding * 2; // restore initial width
1852
1853         return end_pos;
1854 }
1855
1856 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1857         float px = pos.x;
1858         pos.x += hud_fontsize.x * 0.25;
1859         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1860         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1861         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1862         pos.x = px;
1863         pos.y += hud_fontsize.y;
1864
1865         return pos;
1866 }
1867
1868 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1869         float stat_secrets_found, stat_secrets_total;
1870         float stat_monsters_killed, stat_monsters_total;
1871         float rows = 0;
1872         string val;
1873
1874         // get monster stats
1875         stat_monsters_killed = STAT(MONSTERS_KILLED);
1876         stat_monsters_total = STAT(MONSTERS_TOTAL);
1877
1878         // get secrets stats
1879         stat_secrets_found = STAT(SECRETS_FOUND);
1880         stat_secrets_total = STAT(SECRETS_TOTAL);
1881
1882         // get number of rows
1883         if(stat_secrets_total)
1884                 rows += 1;
1885         if(stat_monsters_total)
1886                 rows += 1;
1887
1888         // if no rows, return
1889         if (!rows)
1890                 return pos;
1891
1892         //  draw table header
1893         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1894         pos.y += 1.25 * hud_fontsize.y;
1895         if(panel.current_panel_bg != "0")
1896                 pos.y += panel_bg_border;
1897
1898         panel_pos = pos;
1899         panel_size.y = hud_fontsize.y * rows;
1900         panel_size.y += panel_bg_padding * 2;
1901         HUD_Panel_DrawBg();
1902
1903         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1904         if(panel.current_panel_bg != "0")
1905                 end_pos.y += panel_bg_border * 2;
1906
1907         if(panel_bg_padding)
1908         {
1909                 panel_pos += '1 1 0' * panel_bg_padding;
1910                 panel_size -= '2 2 0' * panel_bg_padding;
1911         }
1912
1913         pos = panel_pos;
1914         vector tmp = panel_size;
1915
1916         if (sbt_bg_alpha)
1917                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1918
1919         // draw monsters
1920         if(stat_monsters_total)
1921         {
1922                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1923                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1924         }
1925
1926         // draw secrets
1927         if(stat_secrets_total)
1928         {
1929                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1930                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1931         }
1932
1933         panel_size.x += panel_bg_padding * 2; // restore initial width
1934         return end_pos;
1935 }
1936
1937 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1938 {
1939         int i;
1940         RANKINGS_RECEIVED_CNT = 0;
1941         for (i=RANKINGS_CNT-1; i>=0; --i)
1942                 if (grecordtime[i])
1943                         ++RANKINGS_RECEIVED_CNT;
1944
1945         if (RANKINGS_RECEIVED_CNT == 0)
1946                 return pos;
1947
1948         vector hl_rgb = rgb + '0.5 0.5 0.5';
1949
1950         vector scoreboard_selected_hl_pos = pos;
1951
1952         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1953         pos.y += 1.25 * hud_fontsize.y;
1954         if(panel.current_panel_bg != "0")
1955                 pos.y += panel_bg_border;
1956
1957         vector scoreboard_selected_hl_size = '0 0 0';
1958         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1959         scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1960
1961         panel_pos = pos;
1962
1963         float namesize = 0;
1964         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1965         {
1966                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1967                 if(f > namesize)
1968                         namesize = f;
1969         }
1970         bool cut = false;
1971         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1972         {
1973                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1974                 cut = true;
1975         }
1976
1977         float ranksize = 3 * hud_fontsize.x;
1978         float timesize = 5 * hud_fontsize.x;
1979         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1980         rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1981         rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1982         if (!rankings_cnt)
1983         {
1984                 rankings_cnt = RANKINGS_RECEIVED_CNT;
1985                 rankings_rows = ceil(rankings_cnt / rankings_columns);
1986         }
1987
1988         // expand name column to fill the entire row
1989         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1990         namesize += available_space;
1991         columnsize.x += available_space;
1992
1993         panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1994         panel_size.y += panel_bg_padding * 2;
1995         scoreboard_selected_hl_size.y += panel_size.y;
1996
1997         HUD_Panel_DrawBg();
1998
1999         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2000         if(panel.current_panel_bg != "0")
2001                 end_pos.y += panel_bg_border * 2;
2002
2003         if(panel_bg_padding)
2004         {
2005                 panel_pos += '1 1 0' * panel_bg_padding;
2006                 panel_size -= '2 2 0' * panel_bg_padding;
2007         }
2008
2009         pos = panel_pos;
2010
2011         if (sbt_bg_alpha)
2012                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2013
2014         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2015         string str = "";
2016         int column = 0, j = 0;
2017         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2018         int start_item = rankings_start_column * rankings_rows;
2019         for(i = start_item; i < start_item + rankings_cnt; ++i)
2020         {
2021                 int t = grecordtime[i];
2022                 if (t == 0)
2023                         continue;
2024
2025                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2026                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2027                 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2028                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2029
2030                 str = count_ordinal(i+1);
2031                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2032                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2033                 str = ColorTranslateRGB(grecordholder[i]);
2034                 if(cut)
2035                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2036                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2037
2038                 pos.y += 1.25 * hud_fontsize.y;
2039                 j++;
2040                 if(j >= rankings_rows)
2041                 {
2042                         column++;
2043                         j = 0;
2044                         pos.x += panel_size.x / rankings_columns;
2045                         pos.y = panel_pos.y;
2046                 }
2047         }
2048         strfree(zoned_name_self);
2049
2050         if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2051         {
2052                 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2053                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2054         }
2055
2056         panel_size.x += panel_bg_padding * 2; // restore initial width
2057         return end_pos;
2058 }
2059
2060 bool have_weapon_stats;
2061 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2062 {
2063         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2064                 return false;
2065         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2066                 return false;
2067
2068         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2069                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2070                 && !intermission)
2071         {
2072                 return false;
2073         }
2074
2075         if (!have_weapon_stats)
2076         {
2077                 FOREACH(Weapons, it != WEP_Null, {
2078                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2079                         if (weapon_stats >= 0)
2080                         {
2081                                 have_weapon_stats = true;
2082                                 break;
2083                         }
2084                 });
2085                 if (!have_weapon_stats)
2086                         return false;
2087         }
2088
2089         return true;
2090 }
2091
2092 bool have_item_stats;
2093 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2094 {
2095         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2096                 return false;
2097         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2098                 return false;
2099
2100         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2101                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2102                 && !intermission)
2103         {
2104                 return false;
2105         }
2106
2107         if (!have_item_stats)
2108         {
2109                 IL_EACH(default_order_items, true, {
2110                         if (!is_item_filtered(it))
2111                         {
2112                                 int q = g_inventory.inv_items[it.m_id];
2113                                 //q = 1; // debug: display all items
2114                                 if (q)
2115                                 {
2116                                         have_item_stats = true;
2117                                         break;
2118                                 }
2119                         }
2120                 });
2121                 if (!have_item_stats)
2122                         return false;
2123         }
2124
2125         return true;
2126 }
2127
2128 vector Scoreboard_Spectators_Draw(vector pos) {
2129
2130         entity pl, tm;
2131         string str = "";
2132
2133         for(pl = players.sort_next; pl; pl = pl.sort_next)
2134         {
2135                 if(pl.team == NUM_SPECTATOR)
2136                 {
2137                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2138                                 if(tm.team == NUM_SPECTATOR)
2139                                         break;
2140                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2141                         draw_beginBoldFont();
2142                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2143                         draw_endBoldFont();
2144                         pos.y += 1.25 * hud_fontsize.y;
2145
2146                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2147                         pos.y += 1.25 * hud_fontsize.y;
2148
2149                         break;
2150                 }
2151         }
2152         if (str != "") // if there's at least one spectator
2153                 pos.y += 0.5 * hud_fontsize.y;
2154
2155         return pos;
2156 }
2157
2158 void Scoreboard_Draw()
2159 {
2160         if(!autocvar__hud_configure)
2161         {
2162                 if(!hud_draw_maximized) return;
2163
2164                 // frametime checks allow to toggle the scoreboard even when the game is paused
2165                 if(scoreboard_active) {
2166                         if (scoreboard_fade_alpha == 0)
2167                                 scoreboard_time = time;
2168                         if(hud_configure_menu_open == 1)
2169                                 scoreboard_fade_alpha = 1;
2170                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2171                         if (scoreboard_fadeinspeed && frametime)
2172                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2173                         else
2174                                 scoreboard_fade_alpha = 1;
2175                         if(hud_fontsize_str != autocvar_hud_fontsize)
2176                         {
2177                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2178                                 Scoreboard_initFieldSizes();
2179                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2180                         }
2181                 }
2182                 else {
2183                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2184                         if (scoreboard_fadeoutspeed && frametime)
2185                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2186                         else
2187                                 scoreboard_fade_alpha = 0;
2188                 }
2189
2190                 if (!scoreboard_fade_alpha)
2191                 {
2192                         scoreboard_acc_fade_alpha = 0;
2193                         scoreboard_itemstats_fade_alpha = 0;
2194                         return;
2195                 }
2196         }
2197         else
2198                 scoreboard_fade_alpha = 0;
2199
2200         if (autocvar_hud_panel_scoreboard_dynamichud)
2201                 HUD_Scale_Enable();
2202         else
2203                 HUD_Scale_Disable();
2204
2205         if(scoreboard_fade_alpha <= 0)
2206                 return;
2207         panel_fade_alpha *= scoreboard_fade_alpha;
2208         HUD_Panel_LoadCvars();
2209
2210         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2211         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2212         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2213         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2214         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2215         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2216         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2217
2218         // don't overlap with con_notify
2219         if(!autocvar__hud_configure)
2220                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2221
2222         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2223         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2224         scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2225         scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2226         panel_pos.x = scoreboard_left;
2227         panel_size.x = fixed_scoreboard_width;
2228
2229         Scoreboard_UpdatePlayerTeams();
2230
2231         scoreboard_top = panel_pos.y;
2232         vector pos = panel_pos;
2233         entity tm;
2234         string str;
2235         vector str_pos;
2236
2237         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2238
2239         // Begin of Game Info Section
2240         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2241         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2242
2243         // Game Info: Game Type
2244         if (scoreboard_ui_enabled == 2)
2245                 str = _("Team Selection");
2246         else
2247                 str = MapInfo_Type_ToText(gametype);
2248         draw_beginBoldFont();
2249         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);
2250         draw_endBoldFont();
2251
2252         pos.y += sb_gameinfo_type_fontsize.y;
2253         // Game Info: Game Detail
2254         if (scoreboard_ui_enabled == 2)
2255         {
2256                 if (scoreboard_selected_team)
2257                         str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2258                 else
2259                         str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2260                 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);
2261
2262                 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2263                 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2264                 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);
2265         }
2266         else
2267         {
2268                 float tl = STAT(TIMELIMIT);
2269                 float fl = STAT(FRAGLIMIT);
2270                 float ll = STAT(LEADLIMIT);
2271                 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2272                 str = "";
2273                 if(tl > 0)
2274                         str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2275                 if(!gametype.m_hidelimits)
2276                 {
2277                         if(fl > 0)
2278                         {
2279                                 if(tl > 0)
2280                                         str = strcat(str, "^7 / "); // delimiter
2281                                 if(teamplay)
2282                                 {
2283                                         str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2284                                                 (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
2285                                                 (teamscores_label(ts_primary) == "fastest") ? "" :
2286                                                 TranslateScoresLabel(teamscores_label(ts_primary))));
2287                                 }
2288                                 else
2289                                 {
2290                                         str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2291                                                 (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
2292                                                 (scores_label(ps_primary) == "fastest") ? "" :
2293                                                 TranslateScoresLabel(scores_label(ps_primary))));
2294                                 }
2295                         }
2296                         if(ll > 0)
2297                         {
2298                                 if(tl > 0 || fl > 0)
2299                                 {
2300                                         // delimiter
2301                                         if (ll_and_fl && fl > 0)
2302                                                 str = strcat(str, "^7 & ");
2303                                         else
2304                                                 str = strcat(str, "^7 / ");
2305                                 }
2306
2307                                 if(teamplay)
2308                                 {
2309                                         str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2310                                                 (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
2311                                                 (teamscores_label(ts_primary) == "fastest") ? "" :
2312                                                 TranslateScoresLabel(teamscores_label(ts_primary))));
2313                                 }
2314                                 else
2315                                 {
2316                                         str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2317                                                 (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
2318                                                 (scores_label(ps_primary) == "fastest") ? "" :
2319                                                 TranslateScoresLabel(scores_label(ps_primary))));
2320                                 }
2321                         }
2322                 }
2323                 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
2324                 // map name
2325                 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2326                 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2327         }
2328         // End of Game Info Section
2329
2330         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2331         if(panel.current_panel_bg != "0")
2332                 pos.y += panel_bg_border;
2333
2334         // Draw the scoreboard
2335         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2336         if(scale <= 0)
2337                 scale = 0.25;
2338         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2339
2340         if(teamplay)
2341         {
2342                 vector panel_bg_color_save = panel_bg_color;
2343                 vector team_score_baseoffset;
2344                 vector team_size_baseoffset;
2345                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2346                 {
2347                         // put team score to the left of scoreboard (and team size to the right)
2348                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2349                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2350                         if(panel.current_panel_bg != "0")
2351                         {
2352                                 team_score_baseoffset.x -= panel_bg_border;
2353                                 team_size_baseoffset.x += panel_bg_border;
2354                         }
2355                 }
2356                 else
2357                 {
2358                         // put team score to the right of scoreboard (and team size to the left)
2359                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2360                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2361                         if(panel.current_panel_bg != "0")
2362                         {
2363                                 team_score_baseoffset.x += panel_bg_border;
2364                                 team_size_baseoffset.x -= panel_bg_border;
2365                         }
2366                 }
2367
2368                 int team_size_total = 0;
2369                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2370                 {
2371                         // calculate team size total (sum of all team sizes)
2372                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2373                                 if(tm.team != NUM_SPECTATOR)
2374                                         team_size_total += tm.team_size;
2375                 }
2376
2377                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2378                 {
2379                         if(tm.team == NUM_SPECTATOR)
2380                                 continue;
2381                         if(!tm.team)
2382                                 continue;
2383
2384                         draw_beginBoldFont();
2385                         vector rgb = Team_ColorRGB(tm.team);
2386                         str = ftos(tm.(teamscores(ts_primary)));
2387                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2388                         {
2389                                 // team score on the left (default)
2390                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2391                         }
2392                         else
2393                         {
2394                                 // team score on the right
2395                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2396                         }
2397                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2398
2399                         // team size (if set to show on the side)
2400                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2401                         {
2402                                 // calculate the starting position for the whole team size info string
2403                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
2404                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2405                                 {
2406                                         // team size on the left
2407                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2408                                 }
2409                                 else
2410                                 {
2411                                         // team size on the right
2412                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2413                                 }
2414                                 str = sprintf("%d", tm.team_size);
2415                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2416                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2417                                 str = sprintf("/%d", team_size_total);
2418                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2419                         }
2420
2421
2422                         // secondary score, e.g. keyhunt
2423                         if(ts_primary != ts_secondary)
2424                         {
2425                                 str = ftos(tm.(teamscores(ts_secondary)));
2426                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2427                                 {
2428                                         // left
2429                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2430                                 }
2431                                 else
2432                                 {
2433                                         // right
2434                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2435                                 }
2436
2437                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2438                         }
2439                         draw_endBoldFont();
2440                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2441                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2442                         else if(panel_bg_color_team > 0)
2443                                 panel_bg_color = rgb * panel_bg_color_team;
2444                         else
2445                                 panel_bg_color = rgb;
2446                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2447                 }
2448                 panel_bg_color = panel_bg_color_save;
2449         }
2450         else
2451         {
2452                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2453                         if(tm.team != NUM_SPECTATOR)
2454                                 break;
2455
2456                 // display it anyway
2457                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2458         }
2459
2460         // draw scoreboard spectators before accuracy and item stats
2461         if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2462                 pos = Scoreboard_Spectators_Draw(pos);
2463         }
2464
2465         // draw accuracy and item stats
2466         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2467                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2468         if (Scoreboard_ItemStats_WouldDraw(pos.y))
2469                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2470
2471         // draw scoreboard spectators after accuracy and item stats and before rankings
2472         if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2473                 pos = Scoreboard_Spectators_Draw(pos);
2474         }
2475
2476         if(MUTATOR_CALLHOOK(ShowRankings)) {
2477                 string ranktitle = M_ARGV(0, string);
2478                 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2479                 if(race_speedaward_alltimebest)
2480                 {
2481                         string name;
2482                         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2483                         str = "";
2484                         if(race_speedaward)
2485                         {
2486                                 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2487                                 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2488                                 str = strcat(str, " / ");
2489                         }
2490                         name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2491                         str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2492                         drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2493                         pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2494                 }
2495                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2496         }
2497         else
2498                 rankings_cnt = 0;
2499
2500         // draw scoreboard spectators after rankings
2501         if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2502                 pos = Scoreboard_Spectators_Draw(pos);
2503         }
2504
2505         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2506
2507         // draw scoreboard spectators after mapstats
2508         if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2509                 pos = Scoreboard_Spectators_Draw(pos);
2510         }
2511
2512
2513         // print information about respawn status
2514         float respawn_time = STAT(RESPAWN_TIME);
2515         if(!intermission)
2516         if(respawn_time)
2517         {
2518                 if(respawn_time < 0)
2519                 {
2520                         // a negative number means we are awaiting respawn, time value is still the same
2521                         respawn_time *= -1; // remove mark now that we checked it
2522
2523                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2524                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2525                         else
2526                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2527                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2528                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2529                                                 :
2530                                                 count_seconds(ceil(respawn_time - time))
2531                                         )
2532                                 );
2533                 }
2534                 else if(time < respawn_time)
2535                 {
2536                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2537                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2538                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2539                                         :
2540                                         count_seconds(ceil(respawn_time - time))
2541                                 )
2542                         );
2543                 }
2544                 else if(time >= respawn_time)
2545                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2546
2547                 pos.y += 1.2 * hud_fontsize.y;
2548                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2549         }
2550
2551         pos.y += hud_fontsize.y;
2552         if (scoreboard_fade_alpha < 1)
2553                 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2554         else if (pos.y != scoreboard_bottom)
2555         {
2556                 if (pos.y > scoreboard_bottom)
2557                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2558                 else
2559                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2560         }
2561
2562         if (rankings_cnt)
2563         {
2564                 if (scoreboard_fade_alpha == 1)
2565                 {
2566                         if (scoreboard_bottom > 0.95 * vid_conheight)
2567                                 rankings_rows = max(1, rankings_rows - 1);
2568                         else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2569                                 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2570                 }
2571                 rankings_cnt = rankings_rows * rankings_columns;
2572         }
2573 }