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