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