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