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