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