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