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