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