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