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