]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Scoreboard: allow showing average scores per round
[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 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
986 {
987         float tmp, num, denom;
988         int f;
989         string str;
990         sbt_field_rgb = '1 1 1';
991         sbt_field_icon0 = "";
992         sbt_field_icon1 = "";
993         sbt_field_icon2 = "";
994         sbt_field_icon0_rgb = '1 1 1';
995         sbt_field_icon1_rgb = '1 1 1';
996         sbt_field_icon2_rgb = '1 1 1';
997         int rounds_played = 0;
998         if (per_round)
999                 rounds_played = pl.(scores(SP_ROUNDS_PL));
1000         switch(field)
1001         {
1002                 case SP_PING:
1003                         if (!pl.gotscores)
1004                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1005                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1006                         f = pl.ping;
1007                         if(f == 0)
1008                                 return _("N/A");
1009                         tmp = max(0, min(220, f-80)) / 220;
1010                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
1011                         return ftos(f);
1012
1013                 case SP_PL:
1014                         if (!pl.gotscores)
1015                                 return _("N/A");
1016                         f = pl.ping_packetloss;
1017                         tmp = pl.ping_movementloss;
1018                         if(f == 0 && tmp == 0)
1019                                 return "";
1020                         str = ftos(ceil(f * 100));
1021                         if(tmp != 0)
1022                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1023                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1024                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1025                         return str;
1026
1027                 case SP_NAME:
1028                         str = Scoreboard_GetName(pl);
1029                         if (autocvar_hud_panel_scoreboard_playerid)
1030                                 str = Scoreboard_AddPlayerId(str, pl);
1031                         return str;
1032
1033                 case SP_FRAGS:
1034                         f = pl.(scores(SP_KILLS));
1035                         f -= pl.(scores(SP_SUICIDES));
1036                         if (rounds_played)
1037                                 return sprintf("%.1f", f / rounds_played);
1038                         return ftos(f);
1039
1040                 case SP_KDRATIO:
1041                         num = pl.(scores(SP_KILLS));
1042                         denom = pl.(scores(SP_DEATHS));
1043
1044                         if(denom == 0) {
1045                                 sbt_field_rgb = '0 1 0';
1046                                 if (rounds_played)
1047                                         str = sprintf("%.1f", num / rounds_played);
1048                                 else
1049                                         str = sprintf("%d", num);
1050                         } else if(num <= 0) {
1051                                 sbt_field_rgb = '1 0 0';
1052                                 if (rounds_played)
1053                                         str = sprintf("%.2f", num / (denom * rounds_played));
1054                                 else
1055                                         str = sprintf("%.1f", num / denom);
1056                         } else
1057                         {
1058                                 if (rounds_played)
1059                                         str = sprintf("%.2f", num / (denom * rounds_played));
1060                                 else
1061                                         str = sprintf("%.1f", num / denom);
1062                         }
1063                         return str;
1064
1065                 case SP_SUM:
1066                         f = pl.(scores(SP_KILLS));
1067                         f -= pl.(scores(SP_DEATHS));
1068
1069                         if(f > 0) {
1070                                 sbt_field_rgb = '0 1 0';
1071                         } else if(f == 0) {
1072                                 sbt_field_rgb = '1 1 1';
1073                         } else {
1074                                 sbt_field_rgb = '1 0 0';
1075                         }
1076                         if (rounds_played)
1077                                 return sprintf("%.1f", f / rounds_played);
1078                         return ftos(f);
1079
1080                 case SP_ELO:
1081                 {
1082                         float elo = pl.(scores(SP_ELO));
1083                         switch (elo) {
1084                                 case -1: return "...";
1085                                 case -2: return _("N/A");
1086                                 default: return ftos(elo);
1087                         }
1088                 }
1089
1090                 case SP_FPS:
1091                 {
1092                         float fps = pl.(scores(SP_FPS));
1093                         if(fps == 0)
1094                         {
1095                                 sbt_field_rgb = '1 1 1';
1096                                 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1097                         }
1098                         //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1099                         sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1100                         return ftos(fps);
1101                 }
1102
1103                 case SP_ROUNDS_PL:
1104                         return ftos(pl.(scores(field)));
1105
1106                 case SP_DMG: case SP_DMGTAKEN:
1107                         if (rounds_played)
1108                                 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1109                         return sprintf("%.1f k", pl.(scores(field)) / 1000);
1110
1111                 default: case SP_SCORE:
1112                         tmp = pl.(scores(field));
1113                         f = scores_flags(field);
1114                         if(field == ps_primary)
1115                                 sbt_field_rgb = '1 1 0';
1116                         else if(field == ps_secondary)
1117                                 sbt_field_rgb = '0 1 1';
1118                         else
1119                                 sbt_field_rgb = '1 1 1';
1120                         return ScoreString(f, tmp, rounds_played);
1121         }
1122         //return "error";
1123 }
1124
1125 float sbt_fixcolumnwidth_len;
1126 float sbt_fixcolumnwidth_iconlen;
1127 float sbt_fixcolumnwidth_marginlen;
1128
1129 string Scoreboard_FixColumnWidth(int i, string str)
1130 {
1131         TC(int, i);
1132         float f;
1133         vector sz;
1134
1135         sbt_fixcolumnwidth_iconlen = 0;
1136
1137         if(sbt_field_icon0 != "")
1138         {
1139                 sz = draw_getimagesize(sbt_field_icon0);
1140                 f = sz.x / sz.y;
1141                 if(sbt_fixcolumnwidth_iconlen < f)
1142                         sbt_fixcolumnwidth_iconlen = f;
1143         }
1144
1145         if(sbt_field_icon1 != "")
1146         {
1147                 sz = draw_getimagesize(sbt_field_icon1);
1148                 f = sz.x / sz.y;
1149                 if(sbt_fixcolumnwidth_iconlen < f)
1150                         sbt_fixcolumnwidth_iconlen = f;
1151         }
1152
1153         if(sbt_field_icon2 != "")
1154         {
1155                 sz = draw_getimagesize(sbt_field_icon2);
1156                 f = sz.x / sz.y;
1157                 if(sbt_fixcolumnwidth_iconlen < f)
1158                         sbt_fixcolumnwidth_iconlen = f;
1159         }
1160
1161         if(sbt_fixcolumnwidth_iconlen != 0)
1162         {
1163                 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1164                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1165         }
1166         else
1167                 sbt_fixcolumnwidth_marginlen = 0;
1168
1169         if(sbt_field[i] == SP_NAME) // name gets all remaining space
1170         {
1171                 int j;
1172                 float remaining_space = 0;
1173                 for(j = 0; j < sbt_num_fields; ++j)
1174                         if(j != i)
1175                                 if (sbt_field[i] != SP_SEPARATOR)
1176                                         remaining_space += sbt_field_size[j] + hud_fontsize.x;
1177                 sbt_field_size[i] = panel_size.x - remaining_space;
1178
1179                 if (sbt_fixcolumnwidth_iconlen != 0)
1180                         remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1181                 float namesize = panel_size.x - remaining_space;
1182                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1183                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1184
1185                 max_namesize = vid_conwidth - remaining_space;
1186         }
1187         else
1188                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1189
1190         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1191         if(sbt_field_size[i] < f)
1192                 sbt_field_size[i] = f;
1193
1194         return str;
1195 }
1196
1197 void Scoreboard_initFieldSizes()
1198 {
1199         for(int i = 0; i < sbt_num_fields; ++i)
1200         {
1201                 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1202                 Scoreboard_FixColumnWidth(i, "");
1203         }
1204 }
1205
1206 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1207 {
1208         int i;
1209         vector column_dim = eY * panel_size.y;
1210         if(other_players)
1211                 column_dim.y -= 1.25 * hud_fontsize.y;
1212         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1213         pos.x += hud_fontsize.x * 0.5;
1214         for(i = 0; i < sbt_num_fields; ++i)
1215         {
1216                 if(sbt_field[i] == SP_SEPARATOR)
1217                         break;
1218                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1219                 if (sbt_highlight)
1220                         if (i % 2)
1221                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1222                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1223                 pos.x += column_dim.x;
1224         }
1225         if(sbt_field[i] == SP_SEPARATOR)
1226         {
1227                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1228                 for(i = sbt_num_fields - 1; i > 0; --i)
1229                 {
1230                         if(sbt_field[i] == SP_SEPARATOR)
1231                                 break;
1232
1233                         pos.x -= sbt_field_size[i];
1234
1235                         if (sbt_highlight)
1236                                 if (!(i % 2))
1237                                 {
1238                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1239                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1240                                 }
1241
1242                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1243                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244                         pos.x -= hud_fontsize.x;
1245                 }
1246         }
1247
1248         pos.x = panel_pos.x;
1249         pos.y += 1.25 * hud_fontsize.y;
1250         return pos;
1251 }
1252
1253 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1254 {
1255         TC(bool, is_self); TC(int, pl_number);
1256         string str;
1257         bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1258
1259         vector h_pos = item_pos;
1260         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1261         // alternated rows highlighting
1262         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1263         {
1264                 if (pl == scoreboard_selected_player)
1265                         drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1266         }
1267         else if(is_self)
1268                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1269         else if((sbt_highlight) && (!(pl_number % 2)))
1270                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1271
1272         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1273
1274         vector pos = item_pos;
1275         // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1276         if (is_self)
1277                 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1278
1279         pos.x += hud_fontsize.x * 0.5;
1280         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1281         vector tmp = '0 0 0';
1282         int i;
1283         PlayerScoreField field;
1284         for(i = 0; i < sbt_num_fields; ++i)
1285         {
1286                 field = sbt_field[i];
1287                 if(field == SP_SEPARATOR)
1288                         break;
1289
1290                 if(is_spec && field != SP_NAME && field != SP_PING) {
1291                         pos.x += sbt_field_size[i] + hud_fontsize.x;
1292                         continue;
1293                 }
1294                 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1295                 str = Scoreboard_FixColumnWidth(i, str);
1296
1297                 pos.x += sbt_field_size[i] + hud_fontsize.x;
1298
1299                 if(field == SP_NAME) {
1300                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1301                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1302                 } else {
1303                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1304                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1305                 }
1306
1307                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1308                 if(sbt_field_icon0 != "")
1309                         drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1310                 if(sbt_field_icon1 != "")
1311                         drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1312                 if(sbt_field_icon2 != "")
1313                         drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1314         }
1315
1316         if(sbt_field[i] == SP_SEPARATOR)
1317         {
1318                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1319                 for(i = sbt_num_fields-1; i > 0; --i)
1320                 {
1321                         field = sbt_field[i];
1322                         if(field == SP_SEPARATOR)
1323                                 break;
1324
1325                         if(is_spec && field != SP_NAME && field != SP_PING) {
1326                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1327                                 continue;
1328                         }
1329
1330                         str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1331                         str = Scoreboard_FixColumnWidth(i, str);
1332
1333                         if(field == SP_NAME) {
1334                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1335                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1336                         } else {
1337                                 tmp.x = sbt_fixcolumnwidth_len;
1338                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1339                         }
1340
1341                         tmp.x = sbt_field_size[i];
1342                         if(sbt_field_icon0 != "")
1343                                 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1344                         if(sbt_field_icon1 != "")
1345                                 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1346                         if(sbt_field_icon2 != "")
1347                                 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1348                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
1349                 }
1350         }
1351
1352         if(pl.eliminated)
1353                 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1354 }
1355
1356 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1357 {
1358         int i = 0;
1359         vector h_pos = item_pos;
1360         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1361
1362         bool complete = (this_team == NUM_SPECTATOR);
1363
1364         if(!complete)
1365         if((sbt_highlight) && (!(pl_number % 2)))
1366                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1367
1368         vector pos = item_pos;
1369         pos.x += hud_fontsize.x * 0.5;
1370         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1371
1372         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1373         if(!complete)
1374                 width_limit -= stringwidth("...", false, hud_fontsize);
1375         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1376         static float max_name_width = 0;
1377         string field = "";
1378         float fieldsize = 0;
1379         float min_fieldsize = 0;
1380         float fieldpadding = hud_fontsize.x * 0.25;
1381         if(this_team == NUM_SPECTATOR)
1382         {
1383                 if(autocvar_hud_panel_scoreboard_spectators_showping)
1384                         min_fieldsize = stringwidth("999", false, hud_fontsize);
1385         }
1386         else if(autocvar_hud_panel_scoreboard_others_showscore)
1387                 min_fieldsize = stringwidth("99", false, hud_fontsize);
1388         for(i = 0; pl; pl = pl.sort_next)
1389         {
1390                 if(pl.team != this_team)
1391                         continue;
1392                 if(pl == ignored_pl)
1393                         continue;
1394
1395                 field = "";
1396                 if(this_team == NUM_SPECTATOR)
1397                 {
1398                         if(autocvar_hud_panel_scoreboard_spectators_showping)
1399                                 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1400                 }
1401                 else if(autocvar_hud_panel_scoreboard_others_showscore)
1402                         field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1403
1404                 string str = entcs_GetName(pl.sv_entnum);
1405                 if (autocvar_hud_panel_scoreboard_playerid)
1406                         str = Scoreboard_AddPlayerId(str, pl);
1407                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1408                 float column_width = stringwidth(str, true, hud_fontsize);
1409                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1410                 {
1411                         if(column_width > max_name_width)
1412                                 max_name_width = column_width;
1413                         column_width = max_name_width;
1414                 }
1415                 if(field != "")
1416                 {
1417                         fieldsize = stringwidth(field, false, hud_fontsize);
1418                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1419                 }
1420
1421                 if(pos.x + column_width > width_limit)
1422                 {
1423                         ++i;
1424                         if(!complete)
1425                         {
1426                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1427                                 break;
1428                         }
1429                         else
1430                         {
1431                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1432                                 pos.y += hud_fontsize.y * 1.25;
1433                         }
1434                 }
1435
1436                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1437                 {
1438                         if (pl == scoreboard_selected_player)
1439                         {
1440                                 h_size.x = column_width + hud_fontsize.x * 0.25;
1441                                 h_size.y = hud_fontsize.y;
1442                                 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1443                         }
1444                 }
1445
1446                 vector name_pos = pos;
1447                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1448                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1449                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1450                 if(field != "")
1451                 {
1452                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1453                         h_size.y = hud_fontsize.y;
1454                         vector field_pos = pos;
1455                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1456                                 field_pos.x += column_width - h_size.x;
1457                         if(sbt_highlight)
1458                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1459                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1460                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1461                 }
1462                 if(pl.eliminated)
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, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1467                 }
1468                 pos.x += column_width;
1469                 pos.x += hud_fontsize.x;
1470         }
1471         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1472 }
1473
1474 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1475 {
1476         int max_players = 999;
1477         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1478         {
1479                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1480                 if(teamplay)
1481                 {
1482                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1483                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1484                         height /= team_count;
1485                 }
1486                 else
1487                         height -= panel_bg_padding * 2; // - padding
1488                 max_players = floor(height / (hud_fontsize.y * 1.25));
1489                 if(max_players <= 1)
1490                         max_players = 1;
1491                 if(max_players == tm.team_size)
1492                         max_players = 999;
1493         }
1494
1495         entity pl;
1496         entity me = playerslots[current_player];
1497         panel_pos = pos;
1498         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1499         panel_size.y += panel_bg_padding * 2;
1500
1501         vector scoreboard_selected_hl_pos = pos;
1502         vector scoreboard_selected_hl_size = '0 0 0';
1503         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1504         scoreboard_selected_hl_size.y = panel_size.y;
1505
1506         HUD_Panel_DrawBg();
1507
1508         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1509         if(panel.current_panel_bg != "0")
1510                 end_pos.y += panel_bg_border * 2;
1511
1512         if(panel_bg_padding)
1513         {
1514                 panel_pos += '1 1 0' * panel_bg_padding;
1515                 panel_size -= '2 2 0' * panel_bg_padding;
1516         }
1517
1518         pos = panel_pos;
1519         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1520
1521         // rounded header
1522         if (sbt_bg_alpha)
1523                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1524
1525         pos.y += 1.25 * hud_fontsize.y;
1526
1527         // table background
1528         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1529         if (sbt_bg_alpha)
1530                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1531
1532
1533         // print header row and highlight columns
1534         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1535
1536         // fill the table and draw the rows
1537         bool is_self = false;
1538         bool self_shown = false;
1539         int i = 0;
1540         for(pl = players.sort_next; pl; pl = pl.sort_next)
1541         {
1542                 if(pl.team != tm.team)
1543                         continue;
1544                 if(i == max_players - 2 && pl != me)
1545                 {
1546                         if(!self_shown && me.team == tm.team)
1547                         {
1548                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1549                                 self_shown = true;
1550                                 pos.y += 1.25 * hud_fontsize.y;
1551                                 ++i;
1552                         }
1553                 }
1554                 if(i >= max_players - 1)
1555                 {
1556                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1557                         break;
1558                 }
1559                 is_self = (pl.sv_entnum == current_player);
1560                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1561                 if(is_self)
1562                         self_shown = true;
1563                 pos.y += 1.25 * hud_fontsize.y;
1564                 ++i;
1565         }
1566
1567         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1568         {
1569                 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1570                 {
1571                         float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1572                         _alpha *= panel_fg_alpha;
1573                         if (_alpha)
1574                                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1575                 }
1576         }
1577
1578         panel_size.x += panel_bg_padding * 2; // restore initial width
1579         return end_pos;
1580 }
1581
1582 bool Scoreboard_WouldDraw()
1583 {
1584         if (scoreboard_ui_enabled)
1585         {
1586                 if (scoreboard_ui_disabling)
1587                 {
1588                         if (scoreboard_fade_alpha == 0)
1589                                 HUD_Scoreboard_UI_Disable_Instantly();
1590                         return false;
1591                 }
1592                 if (intermission && scoreboard_ui_enabled == 2)
1593                 {
1594                         HUD_Scoreboard_UI_Disable_Instantly();
1595                         return false;
1596                 }
1597                 return true;
1598         }
1599         else if (MUTATOR_CALLHOOK(DrawScoreboard))
1600                 return false;
1601         else if (QuickMenu_IsOpened())
1602                 return false;
1603         else if (HUD_Radar_Clickable())
1604                 return false;
1605         else if (sb_showscores) // set by +showscores engine command
1606                 return true;
1607         else if (intermission == 1)
1608                 return true;
1609         else if (intermission == 2)
1610                 return false;
1611         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1612                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1613         {
1614                 return true;
1615         }
1616         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1617                 return true;
1618         return false;
1619 }
1620
1621 float average_accuracy;
1622 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1623 {
1624         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1625
1626         WepSet weapons_stat = WepSet_GetFromStat();
1627         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1628         int disownedcnt = 0;
1629         int nHidden = 0;
1630         FOREACH(Weapons, it != WEP_Null, {
1631                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1632
1633                 WepSet set = it.m_wepset;
1634                 if(it.spawnflags & WEP_TYPE_OTHER)
1635                 {
1636                         ++nHidden;
1637                         continue;
1638                 }
1639                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1640                 {
1641                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1642                                 ++nHidden;
1643                         else
1644                                 ++disownedcnt;
1645                 }
1646         });
1647
1648         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1649         if (weapon_cnt <= 0) return pos;
1650
1651         int rows = 1;
1652         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1653                 rows = 2;
1654         int columns = ceil(weapon_cnt / rows);
1655
1656         float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1657         float weapon_height = hud_fontsize.y * 2.3 / aspect;
1658         float height = weapon_height + hud_fontsize.y;
1659
1660         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);
1661         pos.y += 1.25 * hud_fontsize.y;
1662         if(panel.current_panel_bg != "0")
1663                 pos.y += panel_bg_border;
1664
1665         panel_pos = pos;
1666         panel_size.y = height * rows;
1667         panel_size.y += panel_bg_padding * 2;
1668
1669         float panel_bg_alpha_save = panel_bg_alpha;
1670         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1671         HUD_Panel_DrawBg();
1672         panel_bg_alpha = panel_bg_alpha_save;
1673
1674         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1675         if(panel.current_panel_bg != "0")
1676                 end_pos.y += panel_bg_border * 2;
1677
1678         if(panel_bg_padding)
1679         {
1680                 panel_pos += '1 1 0' * panel_bg_padding;
1681                 panel_size -= '2 2 0' * panel_bg_padding;
1682         }
1683
1684         pos = panel_pos;
1685         vector tmp = panel_size;
1686
1687         float weapon_width = tmp.x / columns / rows;
1688
1689         if (sbt_bg_alpha)
1690                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1691
1692         if(sbt_highlight)
1693         {
1694                 // column highlighting
1695                 for (int i = 0; i < columns; ++i)
1696                         if ((i % 2) == 0)
1697                                 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);
1698
1699                 // row highlighting
1700                 for (int i = 0; i < rows; ++i)
1701                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1702         }
1703
1704         average_accuracy = 0;
1705         int weapons_with_stats = 0;
1706         if (rows == 2)
1707                 pos.x += weapon_width / 2;
1708
1709         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1710                 rgb = '1 1 1';
1711         else
1712                 Accuracy_LoadColors();
1713
1714         float oldposx = pos.x;
1715         vector tmpos = pos;
1716
1717         int column = 0;
1718         FOREACH(Weapons, it != WEP_Null, {
1719                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1720
1721                 WepSet set = it.m_wepset;
1722                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1723                         continue;
1724                 if (it.spawnflags & WEP_TYPE_OTHER)
1725                         continue;
1726
1727                 float weapon_alpha;
1728                 if (weapon_stats >= 0)
1729                         weapon_alpha = sbt_fg_alpha;
1730                 else
1731                         weapon_alpha = 0.2 * sbt_fg_alpha;
1732
1733                 // weapon icon
1734                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1735                 // the accuracy
1736                 if (weapon_stats >= 0) {
1737                         weapons_with_stats += 1;
1738                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1739
1740                         string s = sprintf("%d%%", weapon_stats * 100);
1741                         float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1742
1743                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1744                                 rgb = Accuracy_GetColor(weapon_stats);
1745
1746                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1747                 }
1748                 tmpos.x += weapon_width * rows;
1749                 pos.x += weapon_width * rows;
1750                 if (rows == 2 && column == columns - 1) {
1751                         tmpos.x = oldposx;
1752                         tmpos.y += height;
1753                         pos.y += height;
1754                 }
1755                 ++column;
1756         });
1757
1758         if (weapons_with_stats)
1759                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1760
1761         panel_size.x += panel_bg_padding * 2; // restore initial width
1762
1763         return end_pos;
1764 }
1765
1766 bool is_item_filtered(entity it)
1767 {
1768         if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1769                 return false;
1770         int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1771         if (mask <= 0)
1772                 return false;
1773         if (it.instanceOfArmor || it.instanceOfHealth)
1774         {
1775                 int ha_mask = floor(mask) % 10;
1776                 switch (ha_mask)
1777                 {
1778                         default: return false;
1779                         case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1780                         case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1781                         case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1782                         case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1783                 }
1784         }
1785         if (it.instanceOfAmmo)
1786         {
1787                 int ammo_mask = floor(mask / 10) % 10;
1788                 return (ammo_mask == 1);
1789         }
1790         return false;
1791 }
1792
1793 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1794 {
1795         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1796
1797         int disowned_cnt = 0;
1798         int uninteresting_cnt = 0;
1799         IL_EACH(default_order_items, true, {
1800                 int q = g_inventory.inv_items[it.m_id];
1801                 //q = 1; // debug: display all items
1802                 if (is_item_filtered(it))
1803                         ++uninteresting_cnt;
1804                 else if (!q)
1805                         ++disowned_cnt;
1806         });
1807         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1808         int n = items_cnt - disowned_cnt;
1809         if (n <= 0) return pos;
1810
1811         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1812         int columns = max(6, ceil(n / rows));
1813
1814         float item_height = hud_fontsize.y * 2.3;
1815         float height = item_height + hud_fontsize.y;
1816
1817         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1818         pos.y += 1.25 * hud_fontsize.y;
1819         if(panel.current_panel_bg != "0")
1820                 pos.y += panel_bg_border;
1821
1822         panel_pos = pos;
1823         panel_size.y = height * rows;
1824         panel_size.y += panel_bg_padding * 2;
1825
1826         float panel_bg_alpha_save = panel_bg_alpha;
1827         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1828         HUD_Panel_DrawBg();
1829         panel_bg_alpha = panel_bg_alpha_save;
1830
1831         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1832         if(panel.current_panel_bg != "0")
1833                 end_pos.y += panel_bg_border * 2;
1834
1835         if(panel_bg_padding)
1836         {
1837                 panel_pos += '1 1 0' * panel_bg_padding;
1838                 panel_size -= '2 2 0' * panel_bg_padding;
1839         }
1840
1841         pos = panel_pos;
1842         vector tmp = panel_size;
1843
1844         float item_width = tmp.x / columns / rows;
1845
1846         if (sbt_bg_alpha)
1847                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1848
1849         if(sbt_highlight)
1850         {
1851                 // column highlighting
1852                 for (int i = 0; i < columns; ++i)
1853                         if ((i % 2) == 0)
1854                                 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);
1855
1856                 // row highlighting
1857                 for (int i = 0; i < rows; ++i)
1858                         drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1859         }
1860
1861         if (rows == 2)
1862                 pos.x += item_width / 2;
1863
1864         float oldposx = pos.x;
1865         vector tmpos = pos;
1866
1867         int column = 0;
1868         IL_EACH(default_order_items, !is_item_filtered(it), {
1869                 int n = g_inventory.inv_items[it.m_id];
1870                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1871                 if (n <= 0) continue;
1872                 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);
1873                 string s = ftos(n);
1874                 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1875                 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1876                 tmpos.x += item_width * rows;
1877                 pos.x += item_width * rows;
1878                 if (rows == 2 && column == columns - 1) {
1879                         tmpos.x = oldposx;
1880                         tmpos.y += height;
1881                         pos.y += height;
1882                 }
1883                 ++column;
1884         });
1885
1886         panel_size.x += panel_bg_padding * 2; // restore initial width
1887
1888         return end_pos;
1889 }
1890
1891 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1892         float px = pos.x;
1893         pos.x += hud_fontsize.x * 0.25;
1894         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1895         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1896         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1897         pos.x = px;
1898         pos.y += hud_fontsize.y;
1899
1900         return pos;
1901 }
1902
1903 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1904         float stat_secrets_found, stat_secrets_total;
1905         float stat_monsters_killed, stat_monsters_total;
1906         float rows = 0;
1907         string val;
1908
1909         // get monster stats
1910         stat_monsters_killed = STAT(MONSTERS_KILLED);
1911         stat_monsters_total = STAT(MONSTERS_TOTAL);
1912
1913         // get secrets stats
1914         stat_secrets_found = STAT(SECRETS_FOUND);
1915         stat_secrets_total = STAT(SECRETS_TOTAL);
1916
1917         // get number of rows
1918         if(stat_secrets_total)
1919                 rows += 1;
1920         if(stat_monsters_total)
1921                 rows += 1;
1922
1923         // if no rows, return
1924         if (!rows)
1925                 return pos;
1926
1927         //  draw table header
1928         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1929         pos.y += 1.25 * hud_fontsize.y;
1930         if(panel.current_panel_bg != "0")
1931                 pos.y += panel_bg_border;
1932
1933         panel_pos = pos;
1934         panel_size.y = hud_fontsize.y * rows;
1935         panel_size.y += panel_bg_padding * 2;
1936         HUD_Panel_DrawBg();
1937
1938         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1939         if(panel.current_panel_bg != "0")
1940                 end_pos.y += panel_bg_border * 2;
1941
1942         if(panel_bg_padding)
1943         {
1944                 panel_pos += '1 1 0' * panel_bg_padding;
1945                 panel_size -= '2 2 0' * panel_bg_padding;
1946         }
1947
1948         pos = panel_pos;
1949         vector tmp = panel_size;
1950
1951         if (sbt_bg_alpha)
1952                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1953
1954         // draw monsters
1955         if(stat_monsters_total)
1956         {
1957                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1958                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1959         }
1960
1961         // draw secrets
1962         if(stat_secrets_total)
1963         {
1964                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1965                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1966         }
1967
1968         panel_size.x += panel_bg_padding * 2; // restore initial width
1969         return end_pos;
1970 }
1971
1972 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1973 {
1974         int i;
1975         RANKINGS_RECEIVED_CNT = 0;
1976         for (i=RANKINGS_CNT-1; i>=0; --i)
1977                 if (grecordtime[i])
1978                         ++RANKINGS_RECEIVED_CNT;
1979
1980         if (RANKINGS_RECEIVED_CNT == 0)
1981                 return pos;
1982
1983         vector hl_rgb = rgb + '0.5 0.5 0.5';
1984
1985         vector scoreboard_selected_hl_pos = pos;
1986
1987         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1988         pos.y += 1.25 * hud_fontsize.y;
1989         if(panel.current_panel_bg != "0")
1990                 pos.y += panel_bg_border;
1991
1992         vector scoreboard_selected_hl_size = '0 0 0';
1993         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1994         scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1995
1996         panel_pos = pos;
1997
1998         float namesize = 0;
1999         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2000         {
2001                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2002                 if(f > namesize)
2003                         namesize = f;
2004         }
2005         bool cut = false;
2006         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2007         {
2008                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2009                 cut = true;
2010         }
2011
2012         float ranksize = 3 * hud_fontsize.x;
2013         float timesize = 5 * hud_fontsize.x;
2014         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2015         rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2016         rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2017         if (!rankings_cnt)
2018         {
2019                 rankings_cnt = RANKINGS_RECEIVED_CNT;
2020                 rankings_rows = ceil(rankings_cnt / rankings_columns);
2021         }
2022
2023         // expand name column to fill the entire row
2024         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2025         namesize += available_space;
2026         columnsize.x += available_space;
2027
2028         panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2029         panel_size.y += panel_bg_padding * 2;
2030         scoreboard_selected_hl_size.y += panel_size.y;
2031
2032         HUD_Panel_DrawBg();
2033
2034         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2035         if(panel.current_panel_bg != "0")
2036                 end_pos.y += panel_bg_border * 2;
2037
2038         if(panel_bg_padding)
2039         {
2040                 panel_pos += '1 1 0' * panel_bg_padding;
2041                 panel_size -= '2 2 0' * panel_bg_padding;
2042         }
2043
2044         pos = panel_pos;
2045
2046         if (sbt_bg_alpha)
2047                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2048
2049         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2050         string str = "";
2051         int column = 0, j = 0;
2052         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2053         int start_item = rankings_start_column * rankings_rows;
2054         for(i = start_item; i < start_item + rankings_cnt; ++i)
2055         {
2056                 int t = grecordtime[i];
2057                 if (t == 0)
2058                         continue;
2059
2060                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2061                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2062                 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2063                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2064
2065                 str = count_ordinal(i+1);
2066                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2067                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2068                 str = ColorTranslateRGB(grecordholder[i]);
2069                 if(cut)
2070                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2071                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2072
2073                 pos.y += 1.25 * hud_fontsize.y;
2074                 j++;
2075                 if(j >= rankings_rows)
2076                 {
2077                         column++;
2078                         j = 0;
2079                         pos.x += panel_size.x / rankings_columns;
2080                         pos.y = panel_pos.y;
2081                 }
2082         }
2083         strfree(zoned_name_self);
2084
2085         if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2086         {
2087                 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2088                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2089         }
2090
2091         panel_size.x += panel_bg_padding * 2; // restore initial width
2092         return end_pos;
2093 }
2094
2095 bool have_weapon_stats;
2096 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2097 {
2098         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2099                 return false;
2100         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2101                 return false;
2102
2103         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2104                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2105                 && !intermission)
2106         {
2107                 return false;
2108         }
2109
2110         if (!have_weapon_stats)
2111         {
2112                 FOREACH(Weapons, it != WEP_Null, {
2113                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2114                         if (weapon_stats >= 0)
2115                         {
2116                                 have_weapon_stats = true;
2117                                 break;
2118                         }
2119                 });
2120                 if (!have_weapon_stats)
2121                         return false;
2122         }
2123
2124         return true;
2125 }
2126
2127 bool have_item_stats;
2128 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2129 {
2130         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2131                 return false;
2132         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2133                 return false;
2134
2135         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2136                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2137                 && !intermission)
2138         {
2139                 return false;
2140         }
2141
2142         if (!have_item_stats)
2143         {
2144                 IL_EACH(default_order_items, true, {
2145                         if (!is_item_filtered(it))
2146                         {
2147                                 int q = g_inventory.inv_items[it.m_id];
2148                                 //q = 1; // debug: display all items
2149                                 if (q)
2150                                 {
2151                                         have_item_stats = true;
2152                                         break;
2153                                 }
2154                         }
2155                 });
2156                 if (!have_item_stats)
2157                         return false;
2158         }
2159
2160         return true;
2161 }
2162
2163 vector Scoreboard_Spectators_Draw(vector pos) {
2164
2165         entity pl, tm;
2166         string str = "";
2167
2168         for(pl = players.sort_next; pl; pl = pl.sort_next)
2169         {
2170                 if(pl.team == NUM_SPECTATOR)
2171                 {
2172                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2173                                 if(tm.team == NUM_SPECTATOR)
2174                                         break;
2175                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2176                         draw_beginBoldFont();
2177                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2178                         draw_endBoldFont();
2179                         pos.y += 1.25 * hud_fontsize.y;
2180
2181                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2182                         pos.y += 1.25 * hud_fontsize.y;
2183
2184                         break;
2185                 }
2186         }
2187         if (str != "") // if there's at least one spectator
2188                 pos.y += 0.5 * hud_fontsize.y;
2189
2190         return pos;
2191 }
2192
2193 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2194 {
2195         string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2196         int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2197         return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2198                 (s_label == "score") ? CTX(_("SCO^points")) :
2199                 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2200 }
2201
2202 void Scoreboard_Draw()
2203 {
2204         if(!autocvar__hud_configure)
2205         {
2206                 if(!hud_draw_maximized) return;
2207
2208                 // frametime checks allow to toggle the scoreboard even when the game is paused
2209                 if(scoreboard_active) {
2210                         if (scoreboard_fade_alpha == 0)
2211                                 scoreboard_time = time;
2212                         if(hud_configure_menu_open == 1)
2213                                 scoreboard_fade_alpha = 1;
2214                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2215                         if (scoreboard_fadeinspeed && frametime)
2216                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2217                         else
2218                                 scoreboard_fade_alpha = 1;
2219                         if(hud_fontsize_str != autocvar_hud_fontsize)
2220                         {
2221                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2222                                 Scoreboard_initFieldSizes();
2223                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2224                         }
2225                 }
2226                 else {
2227                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2228                         if (scoreboard_fadeoutspeed && frametime)
2229                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2230                         else
2231                                 scoreboard_fade_alpha = 0;
2232                 }
2233
2234                 if (!scoreboard_fade_alpha)
2235                 {
2236                         scoreboard_acc_fade_alpha = 0;
2237                         scoreboard_itemstats_fade_alpha = 0;
2238                         return;
2239                 }
2240         }
2241         else
2242                 scoreboard_fade_alpha = 0;
2243
2244         if (autocvar_hud_panel_scoreboard_dynamichud)
2245                 HUD_Scale_Enable();
2246         else
2247                 HUD_Scale_Disable();
2248
2249         if(scoreboard_fade_alpha <= 0)
2250                 return;
2251         panel_fade_alpha *= scoreboard_fade_alpha;
2252         HUD_Panel_LoadCvars();
2253
2254         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2255         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2256         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2257         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2258         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2259         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2260         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2261
2262         // don't overlap with con_notify
2263         if(!autocvar__hud_configure)
2264                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2265
2266         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2267         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2268         scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2269         scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2270         panel_pos.x = scoreboard_left;
2271         panel_size.x = fixed_scoreboard_width;
2272
2273         Scoreboard_UpdatePlayerTeams();
2274
2275         scoreboard_top = panel_pos.y;
2276         vector pos = panel_pos;
2277         entity tm;
2278         string str;
2279         vector str_pos;
2280
2281         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2282
2283         // Begin of Game Info Section
2284         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2285         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2286
2287         // Game Info: Game Type
2288         if (scoreboard_ui_enabled == 2)
2289                 str = _("Team Selection");
2290         else
2291                 str = MapInfo_Type_ToText(gametype);
2292         draw_beginBoldFont();
2293         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);
2294         draw_endBoldFont();
2295
2296         pos.y += sb_gameinfo_type_fontsize.y;
2297         // Game Info: Game Detail
2298         if (scoreboard_ui_enabled == 2)
2299         {
2300                 if (scoreboard_selected_team)
2301                         str = sprintf(_("^7Press ^3%s^7 to join the selected team"), getcommandkey(_("jump"), "+jump"));
2302                 else
2303                         str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), getcommandkey(_("jump"), "+jump"));
2304                 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);
2305
2306                 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2307                 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2308                 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);
2309         }
2310         else
2311         {
2312                 float tl = STAT(TIMELIMIT);
2313                 float fl = STAT(FRAGLIMIT);
2314                 float ll = STAT(LEADLIMIT);
2315                 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2316                 str = "";
2317                 if(tl > 0)
2318                         str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2319                 if(!gametype.m_hidelimits)
2320                 {
2321                         if(fl > 0)
2322                         {
2323                                 if(tl > 0)
2324                                         str = strcat(str, "^7 / "); // delimiter
2325                                 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2326                         }
2327                         if(ll > 0)
2328                         {
2329                                 if(tl > 0 || fl > 0)
2330                                 {
2331                                         // delimiter
2332                                         if (ll_and_fl && fl > 0)
2333                                                 str = strcat(str, "^7 & ");
2334                                         else
2335                                                 str = strcat(str, "^7 / ");
2336                                 }
2337                                 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2338                         }
2339                 }
2340                 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
2341                 // map name and player count
2342                 if (campaign)
2343                         str = "";
2344                 else
2345                         str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2346                 str = strcat("^7", _("Map:"), " ^2", mi_shortname, "    ", str); // reusing "Map:" translatable string
2347                 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2348         }
2349         // End of Game Info Section
2350
2351         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2352         if(panel.current_panel_bg != "0")
2353                 pos.y += panel_bg_border;
2354
2355         // Draw the scoreboard
2356         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2357         if(scale <= 0)
2358                 scale = 0.25;
2359         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2360
2361         if(teamplay)
2362         {
2363                 vector panel_bg_color_save = panel_bg_color;
2364                 vector team_score_baseoffset;
2365                 vector team_size_baseoffset;
2366                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2367                 {
2368                         // put team score to the left of scoreboard (and team size to the right)
2369                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2370                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2371                         if(panel.current_panel_bg != "0")
2372                         {
2373                                 team_score_baseoffset.x -= panel_bg_border;
2374                                 team_size_baseoffset.x += panel_bg_border;
2375                         }
2376                 }
2377                 else
2378                 {
2379                         // put team score to the right of scoreboard (and team size to the left)
2380                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2381                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2382                         if(panel.current_panel_bg != "0")
2383                         {
2384                                 team_score_baseoffset.x += panel_bg_border;
2385                                 team_size_baseoffset.x -= panel_bg_border;
2386                         }
2387                 }
2388
2389                 int team_size_total = 0;
2390                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2391                 {
2392                         // calculate team size total (sum of all team sizes)
2393                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2394                                 if(tm.team != NUM_SPECTATOR)
2395                                         team_size_total += tm.team_size;
2396                 }
2397
2398                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2399                 {
2400                         if(tm.team == NUM_SPECTATOR)
2401                                 continue;
2402                         if(!tm.team)
2403                                 continue;
2404
2405                         draw_beginBoldFont();
2406                         vector rgb = Team_ColorRGB(tm.team);
2407                         str = ftos(tm.(teamscores(ts_primary)));
2408                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2409                         {
2410                                 // team score on the left (default)
2411                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2412                         }
2413                         else
2414                         {
2415                                 // team score on the right
2416                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2417                         }
2418                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2419
2420                         // team size (if set to show on the side)
2421                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2422                         {
2423                                 // calculate the starting position for the whole team size info string
2424                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
2425                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2426                                 {
2427                                         // team size on the left
2428                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2429                                 }
2430                                 else
2431                                 {
2432                                         // team size on the right
2433                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2434                                 }
2435                                 str = sprintf("%d", tm.team_size);
2436                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2437                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2438                                 str = sprintf("/%d", team_size_total);
2439                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2440                         }
2441
2442
2443                         // secondary score, e.g. keyhunt
2444                         if(ts_primary != ts_secondary)
2445                         {
2446                                 str = ftos(tm.(teamscores(ts_secondary)));
2447                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2448                                 {
2449                                         // left
2450                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2451                                 }
2452                                 else
2453                                 {
2454                                         // right
2455                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2456                                 }
2457
2458                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2459                         }
2460                         draw_endBoldFont();
2461                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2462                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2463                         else if(panel_bg_color_team > 0)
2464                                 panel_bg_color = rgb * panel_bg_color_team;
2465                         else
2466                                 panel_bg_color = rgb;
2467                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2468                 }
2469                 panel_bg_color = panel_bg_color_save;
2470         }
2471         else
2472         {
2473                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2474                         if(tm.team != NUM_SPECTATOR)
2475                                 break;
2476
2477                 // display it anyway
2478                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2479         }
2480
2481         // draw scoreboard spectators before accuracy and item stats
2482         if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2483                 pos = Scoreboard_Spectators_Draw(pos);
2484         }
2485
2486         // draw accuracy and item stats
2487         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2488                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2489         if (Scoreboard_ItemStats_WouldDraw(pos.y))
2490                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2491
2492         // draw scoreboard spectators after accuracy and item stats and before rankings
2493         if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2494                 pos = Scoreboard_Spectators_Draw(pos);
2495         }
2496
2497         if(MUTATOR_CALLHOOK(ShowRankings)) {
2498                 string ranktitle = M_ARGV(0, string);
2499                 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2500                 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2501                 if(race_speedaward_alltimebest)
2502                 {
2503                         string name;
2504                         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2505                         str = "";
2506                         if(race_speedaward)
2507                         {
2508                                 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2509                                 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2510                                 str = strcat(str, " / ");
2511                         }
2512                         name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2513                         str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2514                         drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2515                         pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2516                 }
2517                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2518         }
2519         else
2520                 rankings_cnt = 0;
2521
2522         // draw scoreboard spectators after rankings
2523         if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2524                 pos = Scoreboard_Spectators_Draw(pos);
2525         }
2526
2527         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2528
2529         // draw scoreboard spectators after mapstats
2530         if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2531                 pos = Scoreboard_Spectators_Draw(pos);
2532         }
2533
2534
2535         // print information about respawn status
2536         float respawn_time = STAT(RESPAWN_TIME);
2537         if(!intermission && respawn_time)
2538         {
2539                 if(respawn_time < 0)
2540                 {
2541                         // a negative number means we are awaiting respawn, time value is still the same
2542                         respawn_time *= -1; // remove mark now that we checked it
2543
2544                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2545                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2546                         else
2547                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2548                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2549                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2550                                                 :
2551                                                 count_seconds(ceil(respawn_time - time))
2552                                         )
2553                                 );
2554                 }
2555                 else if(time < respawn_time)
2556                 {
2557                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2558                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2559                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2560                                         :
2561                                         count_seconds(ceil(respawn_time - time))
2562                                 )
2563                         );
2564                 }
2565                 else if(time >= respawn_time)
2566                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2567
2568                 pos.y += 1.2 * hud_fontsize.y;
2569                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2570         }
2571
2572         pos.y += hud_fontsize.y;
2573         if (scoreboard_fade_alpha < 1)
2574                 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2575         else if (pos.y != scoreboard_bottom)
2576         {
2577                 if (pos.y > scoreboard_bottom)
2578                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2579                 else
2580                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2581         }
2582
2583         if (rankings_cnt)
2584         {
2585                 if (scoreboard_fade_alpha == 1)
2586                 {
2587                         if (scoreboard_bottom > 0.95 * vid_conheight)
2588                                 rankings_rows = max(1, rankings_rows - 1);
2589                         else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2590                                 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2591                 }
2592                 rankings_cnt = rankings_rows * rankings_columns;
2593         }
2594 }