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