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