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