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