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