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