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