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