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