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