]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
6f41ae9ee7ea9e37db33be7de448d3a671a9c107
[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                 if(!STAT(SHOWFPS) && str == "fps")
468                         continue;
469
470                 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
471                 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
472
473                 PlayerScoreField j;
474                 switch(str)
475                 {
476                         case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
477                         case "pl": sbt_field[sbt_num_fields] = SP_PL; 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 "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
481                         case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
482                         case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
483                         case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
484                         case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
485                         case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
486                         default:
487                         {
488                                 FOREACH(Scores, true, {
489                                         if (str == strtolower(scores_label(it))) {
490                                                 j = it;
491                                                 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
492                                         }
493                                 });
494
495 LABEL(notfound)
496                                 if(str == "frags")
497                                         j = SP_FRAGS;
498                                 else
499                                 {
500                                         if(!nocomplain)
501                                                 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
502                                         continue;
503                                 }
504 LABEL(found)
505                                 sbt_field[sbt_num_fields] = j;
506                                 if(j == ps_primary)
507                                         have_primary = true;
508                                 if(j == ps_secondary)
509                                         have_secondary = true;
510
511                         }
512                 }
513                 ++sbt_num_fields;
514                 if(sbt_num_fields >= MAX_SBT_FIELDS)
515                         break;
516         }
517
518         if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
519                 have_primary = true;
520         if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
521                 have_secondary = true;
522         if(ps_primary == ps_secondary)
523                 have_secondary = true;
524         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
525
526         if(sbt_num_fields + missing < MAX_SBT_FIELDS)
527         {
528                 if(!have_name)
529                 {
530                         strunzone(sbt_field_title[sbt_num_fields]);
531                         for(i = sbt_num_fields; i > 0; --i)
532                         {
533                                 sbt_field_title[i] = sbt_field_title[i-1];
534                                 sbt_field_size[i] = sbt_field_size[i-1];
535                                 sbt_field[i] = sbt_field[i-1];
536                         }
537                         sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
538                         sbt_field[0] = SP_NAME;
539                         ++sbt_num_fields;
540                         LOG_INFO("fixed missing field 'name'");
541
542                         if(!have_separator)
543                         {
544                                 strunzone(sbt_field_title[sbt_num_fields]);
545                                 for(i = sbt_num_fields; i > 1; --i)
546                                 {
547                                         sbt_field_title[i] = sbt_field_title[i-1];
548                                         sbt_field_size[i] = sbt_field_size[i-1];
549                                         sbt_field[i] = sbt_field[i-1];
550                                 }
551                                 sbt_field_title[1] = strzone("|");
552                                 sbt_field[1] = SP_SEPARATOR;
553                                 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
554                                 ++sbt_num_fields;
555                                 LOG_INFO("fixed missing field '|'");
556                         }
557                 }
558                 else if(!have_separator)
559                 {
560                         strcpy(sbt_field_title[sbt_num_fields], "|");
561                         sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
562                         sbt_field[sbt_num_fields] = SP_SEPARATOR;
563                         ++sbt_num_fields;
564                         LOG_INFO("fixed missing field '|'");
565                 }
566                 if(!have_secondary)
567                 {
568                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
569                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
570                         sbt_field[sbt_num_fields] = ps_secondary;
571                         ++sbt_num_fields;
572                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
573                 }
574                 if(!have_primary)
575                 {
576                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
577                         sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
578                         sbt_field[sbt_num_fields] = ps_primary;
579                         ++sbt_num_fields;
580                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
581                 }
582         }
583
584         sbt_field[sbt_num_fields] = SP_END;
585 }
586
587 string Scoreboard_AddPlayerId(string pl_name, entity pl)
588 {
589         string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
590         string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
591         return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
592 }
593
594 // MOVEUP::
595 vector sbt_field_rgb;
596 string sbt_field_icon0;
597 string sbt_field_icon1;
598 string sbt_field_icon2;
599 vector sbt_field_icon0_rgb;
600 vector sbt_field_icon1_rgb;
601 vector sbt_field_icon2_rgb;
602 string Scoreboard_GetName(entity pl)
603 {
604         if(ready_waiting && pl.ready)
605         {
606                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
607         }
608         else if(!teamplay)
609         {
610                 int f = entcs_GetClientColors(pl.sv_entnum);
611                 {
612                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
613                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
614                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
615                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
616                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617                 }
618         }
619         return entcs_GetName(pl.sv_entnum);
620 }
621
622 string Scoreboard_GetField(entity pl, PlayerScoreField field)
623 {
624         float tmp, num, denom;
625         int f;
626         string str;
627         sbt_field_rgb = '1 1 1';
628         sbt_field_icon0 = "";
629         sbt_field_icon1 = "";
630         sbt_field_icon2 = "";
631         sbt_field_icon0_rgb = '1 1 1';
632         sbt_field_icon1_rgb = '1 1 1';
633         sbt_field_icon2_rgb = '1 1 1';
634         switch(field)
635         {
636                 case SP_PING:
637                         if (!pl.gotscores)
638                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
639                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
640                         f = pl.ping;
641                         if(f == 0)
642                                 return _("N/A");
643                         tmp = max(0, min(220, f-80)) / 220;
644                         sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
645                         return ftos(f);
646
647                 case SP_PL:
648                         if (!pl.gotscores)
649                                 return _("N/A");
650                         f = pl.ping_packetloss;
651                         tmp = pl.ping_movementloss;
652                         if(f == 0 && tmp == 0)
653                                 return "";
654                         str = ftos(ceil(f * 100));
655                         if(tmp != 0)
656                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
657                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
658                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
659                         return str;
660
661                 case SP_NAME:
662                         str = Scoreboard_GetName(pl);
663                         if (autocvar_hud_panel_scoreboard_playerid)
664                                 str = Scoreboard_AddPlayerId(str, pl);
665                         return str;
666
667                 case SP_FRAGS:
668                         f = pl.(scores(SP_KILLS));
669                         f -= pl.(scores(SP_SUICIDES));
670                         return ftos(f);
671
672                 case SP_KDRATIO:
673                         num = pl.(scores(SP_KILLS));
674                         denom = pl.(scores(SP_DEATHS));
675
676                         if(denom == 0) {
677                                 sbt_field_rgb = '0 1 0';
678                                 str = sprintf("%d", num);
679                         } else if(num <= 0) {
680                                 sbt_field_rgb = '1 0 0';
681                                 str = sprintf("%.1f", num/denom);
682                         } else
683                                 str = sprintf("%.1f", num/denom);
684                         return str;
685
686                 case SP_SUM:
687                         f = pl.(scores(SP_KILLS));
688                         f -= pl.(scores(SP_DEATHS));
689
690                         if(f > 0) {
691                                 sbt_field_rgb = '0 1 0';
692                         } else if(f == 0) {
693                                 sbt_field_rgb = '1 1 1';
694                         } else {
695                                 sbt_field_rgb = '1 0 0';
696                         }
697                         return ftos(f);
698
699                 case SP_ELO:
700                 {
701                         float elo = pl.(scores(SP_ELO));
702                         switch (elo) {
703                                 case -1: return "...";
704                                 case -2: return _("N/A");
705                                 default: return ftos(elo);
706                         }
707                 }
708
709                 case SP_FPS:
710                 {
711                         float fps = pl.(scores(SP_FPS));
712                         if(fps == 0)
713                         {
714                                 sbt_field_rgb = '1 1 1';
715                                 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
716                         }
717                         //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
718                         sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
719                         return ftos(fps);
720                 }
721
722                 case SP_DMG: case SP_DMGTAKEN:
723                         return sprintf("%.1f k", pl.(scores(field)) / 1000);
724
725                 default: case SP_SCORE:
726                         tmp = pl.(scores(field));
727                         f = scores_flags(field);
728                         if(field == ps_primary)
729                                 sbt_field_rgb = '1 1 0';
730                         else if(field == ps_secondary)
731                                 sbt_field_rgb = '0 1 1';
732                         else
733                                 sbt_field_rgb = '1 1 1';
734                         return ScoreString(f, tmp);
735         }
736         //return "error";
737 }
738
739 float sbt_fixcolumnwidth_len;
740 float sbt_fixcolumnwidth_iconlen;
741 float sbt_fixcolumnwidth_marginlen;
742
743 string Scoreboard_FixColumnWidth(int i, string str)
744 {
745         TC(int, i);
746         float f;
747         vector sz;
748
749         sbt_fixcolumnwidth_iconlen = 0;
750
751         if(sbt_field_icon0 != "")
752         {
753                 sz = draw_getimagesize(sbt_field_icon0);
754                 f = sz.x / sz.y;
755                 if(sbt_fixcolumnwidth_iconlen < f)
756                         sbt_fixcolumnwidth_iconlen = f;
757         }
758
759         if(sbt_field_icon1 != "")
760         {
761                 sz = draw_getimagesize(sbt_field_icon1);
762                 f = sz.x / sz.y;
763                 if(sbt_fixcolumnwidth_iconlen < f)
764                         sbt_fixcolumnwidth_iconlen = f;
765         }
766
767         if(sbt_field_icon2 != "")
768         {
769                 sz = draw_getimagesize(sbt_field_icon2);
770                 f = sz.x / sz.y;
771                 if(sbt_fixcolumnwidth_iconlen < f)
772                         sbt_fixcolumnwidth_iconlen = f;
773         }
774
775         if(sbt_fixcolumnwidth_iconlen != 0)
776         {
777                 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
778                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
779         }
780         else
781                 sbt_fixcolumnwidth_marginlen = 0;
782
783         if(sbt_field[i] == SP_NAME) // name gets all remaining space
784         {
785                 int j;
786                 float remaining_space = 0;
787                 for(j = 0; j < sbt_num_fields; ++j)
788                         if(j != i)
789                                 if (sbt_field[i] != SP_SEPARATOR)
790                                         remaining_space += sbt_field_size[j] + hud_fontsize.x;
791                 sbt_field_size[i] = panel_size.x - remaining_space;
792
793                 if (sbt_fixcolumnwidth_iconlen != 0)
794                         remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
795                 float namesize = panel_size.x - remaining_space;
796                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
797                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
798
799                 max_namesize = vid_conwidth - remaining_space;
800         }
801         else
802                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
803
804         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
805         if(sbt_field_size[i] < f)
806                 sbt_field_size[i] = f;
807
808         return str;
809 }
810
811 void Scoreboard_initFieldSizes()
812 {
813         for(int i = 0; i < sbt_num_fields; ++i)
814         {
815                 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
816                 Scoreboard_FixColumnWidth(i, "");
817         }
818 }
819
820 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
821 {
822         int i;
823         vector column_dim = eY * panel_size.y;
824         if(other_players)
825                 column_dim.y -= 1.25 * hud_fontsize.y;
826         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
827         pos.x += hud_fontsize.x * 0.5;
828         for(i = 0; i < sbt_num_fields; ++i)
829         {
830                 if(sbt_field[i] == SP_SEPARATOR)
831                         break;
832                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
833                 if (sbt_highlight)
834                         if (i % 2)
835                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
836                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
837                 pos.x += column_dim.x;
838         }
839         if(sbt_field[i] == SP_SEPARATOR)
840         {
841                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
842                 for(i = sbt_num_fields - 1; i > 0; --i)
843                 {
844                         if(sbt_field[i] == SP_SEPARATOR)
845                                 break;
846
847                         pos.x -= sbt_field_size[i];
848
849                         if (sbt_highlight)
850                                 if (!(i % 2))
851                                 {
852                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
853                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
854                                 }
855
856                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
857                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
858                         pos.x -= hud_fontsize.x;
859                 }
860         }
861
862         pos.x = panel_pos.x;
863         pos.y += 1.25 * hud_fontsize.y;
864         return pos;
865 }
866
867 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
868 {
869         TC(bool, is_self); TC(int, pl_number);
870         string str;
871         bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
872
873         vector h_pos = item_pos;
874         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
875         // alternated rows highlighting
876         if(is_self)
877                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
878         else if((sbt_highlight) && (!(pl_number % 2)))
879                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
880
881         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
882
883         vector pos = item_pos;
884         // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
885         if (is_self)
886                 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
887
888         pos.x += hud_fontsize.x * 0.5;
889         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
890         vector tmp = '0 0 0';
891         int i;
892         PlayerScoreField field;
893         for(i = 0; i < sbt_num_fields; ++i)
894         {
895                 field = sbt_field[i];
896                 if(field == SP_SEPARATOR)
897                         break;
898
899                 if(is_spec && field != SP_NAME && field != SP_PING) {
900                         pos.x += sbt_field_size[i] + hud_fontsize.x;
901                         continue;
902                 }
903                 str = Scoreboard_GetField(pl, field);
904                 str = Scoreboard_FixColumnWidth(i, str);
905
906                 pos.x += sbt_field_size[i] + hud_fontsize.x;
907
908                 if(field == SP_NAME) {
909                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
910                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
911                 } else {
912                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
913                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
914                 }
915
916                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
917                 if(sbt_field_icon0 != "")
918                         drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
919                 if(sbt_field_icon1 != "")
920                         drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
921                 if(sbt_field_icon2 != "")
922                         drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
923         }
924
925         if(sbt_field[i] == SP_SEPARATOR)
926         {
927                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
928                 for(i = sbt_num_fields-1; i > 0; --i)
929                 {
930                         field = sbt_field[i];
931                         if(field == SP_SEPARATOR)
932                                 break;
933
934                         if(is_spec && field != SP_NAME && field != SP_PING) {
935                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
936                                 continue;
937                         }
938
939                         str = Scoreboard_GetField(pl, field);
940                         str = Scoreboard_FixColumnWidth(i, str);
941
942                         if(field == SP_NAME) {
943                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
944                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
945                         } else {
946                                 tmp.x = sbt_fixcolumnwidth_len;
947                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
948                         }
949
950                         tmp.x = sbt_field_size[i];
951                         if(sbt_field_icon0 != "")
952                                 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
953                         if(sbt_field_icon1 != "")
954                                 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
955                         if(sbt_field_icon2 != "")
956                                 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
957                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
958                 }
959         }
960
961         if(pl.eliminated)
962                 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
963 }
964
965 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
966 {
967         int i = 0;
968         vector h_pos = item_pos;
969         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
970
971         bool complete = (this_team == NUM_SPECTATOR);
972
973         if(!complete)
974         if((sbt_highlight) && (!(pl_number % 2)))
975                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
976
977         vector pos = item_pos;
978         pos.x += hud_fontsize.x * 0.5;
979         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
980
981         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
982         if(!complete)
983                 width_limit -= stringwidth("...", false, hud_fontsize);
984         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
985         static float max_name_width = 0;
986         string field = "";
987         float fieldsize = 0;
988         float min_fieldsize = 0;
989         float fieldpadding = hud_fontsize.x * 0.25;
990         if(this_team == NUM_SPECTATOR)
991         {
992                 if(autocvar_hud_panel_scoreboard_spectators_showping)
993                         min_fieldsize = stringwidth("999", false, hud_fontsize);
994         }
995         else if(autocvar_hud_panel_scoreboard_others_showscore)
996                 min_fieldsize = stringwidth("99", false, hud_fontsize);
997         for(i = 0; pl; pl = pl.sort_next)
998         {
999                 if(pl.team != this_team)
1000                         continue;
1001                 if(pl == ignored_pl)
1002                         continue;
1003
1004                 field = "";
1005                 if(this_team == NUM_SPECTATOR)
1006                 {
1007                         if(autocvar_hud_panel_scoreboard_spectators_showping)
1008                                 field = Scoreboard_GetField(pl, SP_PING);
1009                 }
1010                 else if(autocvar_hud_panel_scoreboard_others_showscore)
1011                         field = Scoreboard_GetField(pl, SP_SCORE);
1012
1013                 string str = entcs_GetName(pl.sv_entnum);
1014                 if (autocvar_hud_panel_scoreboard_playerid)
1015                         str = Scoreboard_AddPlayerId(str, pl);
1016                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1017                 float column_width = stringwidth(str, true, hud_fontsize);
1018                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1019                 {
1020                         if(column_width > max_name_width)
1021                                 max_name_width = column_width;
1022                         column_width = max_name_width;
1023                 }
1024                 if(field != "")
1025                 {
1026                         fieldsize = stringwidth(field, false, hud_fontsize);
1027                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1028                 }
1029
1030                 if(pos.x + column_width > width_limit)
1031                 {
1032                         ++i;
1033                         if(!complete)
1034                         {
1035                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1036                                 break;
1037                         }
1038                         else
1039                         {
1040                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1041                                 pos.y += hud_fontsize.y * 1.25;
1042                         }
1043                 }
1044
1045                 vector name_pos = pos;
1046                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1047                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1048                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1049                 if(field != "")
1050                 {
1051                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1052                         h_size.y = hud_fontsize.y;
1053                         vector field_pos = pos;
1054                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1055                                 field_pos.x += column_width - h_size.x;
1056                         if(sbt_highlight)
1057                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1058                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1059                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1060                 }
1061                 if(pl.eliminated)
1062                 {
1063                         h_size.x = column_width + hud_fontsize.x * 0.25;
1064                         h_size.y = hud_fontsize.y;
1065                         drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1066                 }
1067                 pos.x += column_width;
1068                 pos.x += hud_fontsize.x;
1069         }
1070         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1071 }
1072
1073 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1074 {
1075         int max_players = 999;
1076         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1077         {
1078                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1079                 if(teamplay)
1080                 {
1081                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1082                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1083                         height /= team_count;
1084                 }
1085                 else
1086                         height -= panel_bg_padding * 2; // - padding
1087                 max_players = floor(height / (hud_fontsize.y * 1.25));
1088                 if(max_players <= 1)
1089                         max_players = 1;
1090                 if(max_players == tm.team_size)
1091                         max_players = 999;
1092         }
1093
1094         entity pl;
1095         entity me = playerslots[current_player];
1096         panel_pos = pos;
1097         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1098         panel_size.y += panel_bg_padding * 2;
1099         HUD_Panel_DrawBg();
1100
1101         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1102         if(panel.current_panel_bg != "0")
1103                 end_pos.y += panel_bg_border * 2;
1104
1105         if(panel_bg_padding)
1106         {
1107                 panel_pos += '1 1 0' * panel_bg_padding;
1108                 panel_size -= '2 2 0' * panel_bg_padding;
1109         }
1110
1111         pos = panel_pos;
1112         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1113
1114         // rounded header
1115         if (sbt_bg_alpha)
1116                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1117
1118         pos.y += 1.25 * hud_fontsize.y;
1119
1120         // table background
1121         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1122         if (sbt_bg_alpha)
1123                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1124
1125
1126         // print header row and highlight columns
1127         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1128
1129         // fill the table and draw the rows
1130         bool is_self = false;
1131         bool self_shown = false;
1132         int i = 0;
1133         for(pl = players.sort_next; pl; pl = pl.sort_next)
1134         {
1135                 if(pl.team != tm.team)
1136                         continue;
1137                 if(i == max_players - 2 && pl != me)
1138                 {
1139                         if(!self_shown && me.team == tm.team)
1140                         {
1141                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1142                                 self_shown = true;
1143                                 pos.y += 1.25 * hud_fontsize.y;
1144                                 ++i;
1145                         }
1146                 }
1147                 if(i >= max_players - 1)
1148                 {
1149                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1150                         break;
1151                 }
1152                 is_self = (pl.sv_entnum == current_player);
1153                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1154                 if(is_self)
1155                         self_shown = true;
1156                 pos.y += 1.25 * hud_fontsize.y;
1157                 ++i;
1158         }
1159
1160         panel_size.x += panel_bg_padding * 2; // restore initial width
1161         return end_pos;
1162 }
1163
1164 bool Scoreboard_WouldDraw()
1165 {
1166         if (MUTATOR_CALLHOOK(DrawScoreboard))
1167                 return false;
1168         else if (QuickMenu_IsOpened())
1169                 return false;
1170         else if (HUD_Radar_Clickable())
1171                 return false;
1172         else if (scoreboard_showscores)
1173                 return true;
1174         else if (intermission == 1)
1175                 return true;
1176         else if (intermission == 2)
1177                 return false;
1178         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1179                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1180         {
1181                 return true;
1182         }
1183         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1184                 return true;
1185         return false;
1186 }
1187
1188 float average_accuracy;
1189 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1190 {
1191         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1192
1193         WepSet weapons_stat = WepSet_GetFromStat();
1194         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1195         int disownedcnt = 0;
1196         int nHidden = 0;
1197         FOREACH(Weapons, it != WEP_Null, {
1198                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1199
1200                 WepSet set = it.m_wepset;
1201                 if(it.spawnflags & WEP_TYPE_OTHER)
1202                 {
1203                         ++nHidden;
1204                         continue;
1205                 }
1206                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1207                 {
1208                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1209                                 ++nHidden;
1210                         else
1211                                 ++disownedcnt;
1212                 }
1213         });
1214
1215         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1216         if (weapon_cnt <= 0) return pos;
1217
1218         int rows = 1;
1219         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1220                 rows = 2;
1221         int columnns = ceil(weapon_cnt / rows);
1222
1223         float weapon_height = 29;
1224         float height = hud_fontsize.y + weapon_height;
1225
1226         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);
1227         pos.y += 1.25 * hud_fontsize.y;
1228         if(panel.current_panel_bg != "0")
1229                 pos.y += panel_bg_border;
1230
1231         panel_pos = pos;
1232         panel_size.y = height * rows;
1233         panel_size.y += panel_bg_padding * 2;
1234
1235         float panel_bg_alpha_save = panel_bg_alpha;
1236         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1237         HUD_Panel_DrawBg();
1238         panel_bg_alpha = panel_bg_alpha_save;
1239
1240         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1241         if(panel.current_panel_bg != "0")
1242                 end_pos.y += panel_bg_border * 2;
1243
1244         if(panel_bg_padding)
1245         {
1246                 panel_pos += '1 1 0' * panel_bg_padding;
1247                 panel_size -= '2 2 0' * panel_bg_padding;
1248         }
1249
1250         pos = panel_pos;
1251         vector tmp = panel_size;
1252
1253         float weapon_width = tmp.x / columnns / rows;
1254
1255         if (sbt_bg_alpha)
1256                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1257
1258         if(sbt_highlight)
1259         {
1260                 // column highlighting
1261                 for (int i = 0; i < columnns; ++i)
1262                         if ((i % 2) == 0)
1263                                 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);
1264
1265                 // row highlighting
1266                 for (int i = 0; i < rows; ++i)
1267                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1268         }
1269
1270         average_accuracy = 0;
1271         int weapons_with_stats = 0;
1272         if (rows == 2)
1273                 pos.x += weapon_width / 2;
1274
1275         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1276                 rgb = '1 1 1';
1277         else
1278                 Accuracy_LoadColors();
1279
1280         float oldposx = pos.x;
1281         vector tmpos = pos;
1282
1283         int column = 0;
1284         FOREACH(Weapons, it != WEP_Null, {
1285                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1286
1287                 WepSet set = it.m_wepset;
1288                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1289                         continue;
1290                 if (it.spawnflags & WEP_TYPE_OTHER)
1291                         continue;
1292
1293                 float weapon_alpha;
1294                 if (weapon_stats >= 0)
1295                         weapon_alpha = sbt_fg_alpha;
1296                 else
1297                         weapon_alpha = 0.2 * sbt_fg_alpha;
1298
1299                 // weapon icon
1300                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1301                 // the accuracy
1302                 if (weapon_stats >= 0) {
1303                         weapons_with_stats += 1;
1304                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1305
1306                         string s;
1307                         s = sprintf("%d%%", weapon_stats * 100);
1308
1309                         float padding;
1310                         padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1311
1312                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1313                                 rgb = Accuracy_GetColor(weapon_stats);
1314
1315                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1316                 }
1317                 tmpos.x += weapon_width * rows;
1318                 pos.x += weapon_width * rows;
1319                 if (rows == 2 && column == columnns - 1) {
1320                         tmpos.x = oldposx;
1321                         tmpos.y += height;
1322                         pos.y += height;
1323                 }
1324                 ++column;
1325         });
1326
1327         if (weapons_with_stats)
1328                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1329
1330         panel_size.x += panel_bg_padding * 2; // restore initial width
1331
1332         return end_pos;
1333 }
1334
1335 .bool uninteresting;
1336 STATIC_INIT(default_order_items_label)
1337 {
1338         IL_EACH(default_order_items, true, {
1339                 if(!(it.instanceOfPowerup
1340                         || it == ITEM_HealthMega || it == ITEM_HealthBig
1341                         || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1342                         ))
1343                 {
1344                         it.uninteresting = true;
1345                 }
1346         });
1347 }
1348
1349 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1350 {
1351         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1352
1353         int disowned_cnt = 0;
1354         int uninteresting_cnt = 0;
1355         IL_EACH(default_order_items, true, {
1356                 int q = g_inventory.inv_items[it.m_id];
1357                 //q = 1; // debug: display all items
1358                 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1359                         ++uninteresting_cnt;
1360                 else if (!q)
1361                         ++disowned_cnt;
1362         });
1363         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1364         int n = items_cnt - disowned_cnt;
1365         if (n <= 0) return pos;
1366
1367         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1368         int columnns = max(6, ceil(n / rows));
1369
1370         float height = 40;
1371         float fontsize = height * 1/3;
1372         float item_height = height * 2/3;
1373
1374         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1375         pos.y += 1.25 * hud_fontsize.y;
1376         if(panel.current_panel_bg != "0")
1377                 pos.y += panel_bg_border;
1378
1379         panel_pos = pos;
1380         panel_size.y = height * rows;
1381         panel_size.y += panel_bg_padding * 2;
1382
1383         float panel_bg_alpha_save = panel_bg_alpha;
1384         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1385         HUD_Panel_DrawBg();
1386         panel_bg_alpha = panel_bg_alpha_save;
1387
1388         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1389         if(panel.current_panel_bg != "0")
1390                 end_pos.y += panel_bg_border * 2;
1391
1392         if(panel_bg_padding)
1393         {
1394                 panel_pos += '1 1 0' * panel_bg_padding;
1395                 panel_size -= '2 2 0' * panel_bg_padding;
1396         }
1397
1398         pos = panel_pos;
1399         vector tmp = panel_size;
1400
1401         float item_width = tmp.x / columnns / rows;
1402
1403         if (sbt_bg_alpha)
1404                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1405
1406         if(sbt_highlight)
1407         {
1408                 // column highlighting
1409                 for (int i = 0; i < columnns; ++i)
1410                         if ((i % 2) == 0)
1411                                 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);
1412
1413                 // row highlighting
1414                 for (int i = 0; i < rows; ++i)
1415                         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);
1416         }
1417
1418         if (rows == 2)
1419                 pos.x += item_width / 2;
1420
1421         float oldposx = pos.x;
1422         vector tmpos = pos;
1423
1424         int column = 0;
1425         IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1426                 int n = g_inventory.inv_items[it.m_id];
1427                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1428                 if (n <= 0) continue;
1429                 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);
1430                 string s = ftos(n);
1431                 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1432                 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);
1433                 tmpos.x += item_width * rows;
1434                 pos.x += item_width * rows;
1435                 if (rows == 2 && column == columnns - 1) {
1436                         tmpos.x = oldposx;
1437                         tmpos.y += height;
1438                         pos.y += height;
1439                 }
1440                 ++column;
1441         });
1442
1443         panel_size.x += panel_bg_padding * 2; // restore initial width
1444
1445         return end_pos;
1446 }
1447
1448 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1449         float px = pos.x;
1450         pos.x += hud_fontsize.x * 0.25;
1451         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1453         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1454         pos.x = px;
1455         pos.y += hud_fontsize.y;
1456
1457         return pos;
1458 }
1459
1460 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1461         float stat_secrets_found, stat_secrets_total;
1462         float stat_monsters_killed, stat_monsters_total;
1463         float rows = 0;
1464         string val;
1465
1466         // get monster stats
1467         stat_monsters_killed = STAT(MONSTERS_KILLED);
1468         stat_monsters_total = STAT(MONSTERS_TOTAL);
1469
1470         // get secrets stats
1471         stat_secrets_found = STAT(SECRETS_FOUND);
1472         stat_secrets_total = STAT(SECRETS_TOTAL);
1473
1474         // get number of rows
1475         if(stat_secrets_total)
1476                 rows += 1;
1477         if(stat_monsters_total)
1478                 rows += 1;
1479
1480         // if no rows, return
1481         if (!rows)
1482                 return pos;
1483
1484         //  draw table header
1485         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1486         pos.y += 1.25 * hud_fontsize.y;
1487         if(panel.current_panel_bg != "0")
1488                 pos.y += panel_bg_border;
1489
1490         panel_pos = pos;
1491         panel_size.y = hud_fontsize.y * rows;
1492         panel_size.y += panel_bg_padding * 2;
1493         HUD_Panel_DrawBg();
1494
1495         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1496         if(panel.current_panel_bg != "0")
1497                 end_pos.y += panel_bg_border * 2;
1498
1499         if(panel_bg_padding)
1500         {
1501                 panel_pos += '1 1 0' * panel_bg_padding;
1502                 panel_size -= '2 2 0' * panel_bg_padding;
1503         }
1504
1505         pos = panel_pos;
1506         vector tmp = panel_size;
1507
1508         if (sbt_bg_alpha)
1509                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1510
1511         // draw monsters
1512         if(stat_monsters_total)
1513         {
1514                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1515                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1516         }
1517
1518         // draw secrets
1519         if(stat_secrets_total)
1520         {
1521                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1522                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1523         }
1524
1525         panel_size.x += panel_bg_padding * 2; // restore initial width
1526         return end_pos;
1527 }
1528
1529
1530 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1531 {
1532         int i;
1533         RANKINGS_RECEIVED_CNT = 0;
1534         for (i=RANKINGS_CNT-1; i>=0; --i)
1535                 if (grecordtime[i])
1536                         ++RANKINGS_RECEIVED_CNT;
1537
1538         if (RANKINGS_RECEIVED_CNT == 0)
1539                 return pos;
1540
1541         vector hl_rgb = rgb + '0.5 0.5 0.5';
1542
1543         pos.y += hud_fontsize.y;
1544         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1545         pos.y += 1.25 * hud_fontsize.y;
1546         if(panel.current_panel_bg != "0")
1547                 pos.y += panel_bg_border;
1548
1549         panel_pos = pos;
1550
1551         float namesize = 0;
1552         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1553         {
1554                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1555                 if(f > namesize)
1556                         namesize = f;
1557         }
1558         bool cut = false;
1559         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1560         {
1561                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1562                 cut = true;
1563         }
1564
1565         float ranksize = 3 * hud_fontsize.x;
1566         float timesize = 5 * hud_fontsize.x;
1567         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1568         int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1569         columns = min(columns, RANKINGS_RECEIVED_CNT);
1570
1571         // expand name column to fill the entire row
1572         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1573         namesize += available_space;
1574         columnsize.x += available_space;
1575
1576         panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1577         panel_size.y += panel_bg_padding * 2;
1578
1579         HUD_Panel_DrawBg();
1580
1581         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1582         if(panel.current_panel_bg != "0")
1583                 end_pos.y += panel_bg_border * 2;
1584
1585         if(panel_bg_padding)
1586         {
1587                 panel_pos += '1 1 0' * panel_bg_padding;
1588                 panel_size -= '2 2 0' * panel_bg_padding;
1589         }
1590
1591         pos = panel_pos;
1592
1593         if (sbt_bg_alpha)
1594                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1595
1596         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1597         string str = "";
1598         int column = 0, j = 0;
1599         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1600         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1601         {
1602                 float t;
1603                 t = grecordtime[i];
1604                 if (t == 0)
1605                         continue;
1606
1607                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1608                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1609                 else if(!((j + column) & 1) && sbt_highlight)
1610                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1611
1612                 str = count_ordinal(i+1);
1613                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1614                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1615                 str = ColorTranslateRGB(grecordholder[i]);
1616                 if(cut)
1617                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1618                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1619
1620                 pos.y += 1.25 * hud_fontsize.y;
1621                 j++;
1622                 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1623                 {
1624                         column++;
1625                         j = 0;
1626                         pos.x += panel_size.x / columns;
1627                         pos.y = panel_pos.y;
1628                 }
1629         }
1630         strfree(zoned_name_self);
1631
1632         panel_size.x += panel_bg_padding * 2; // restore initial width
1633         return end_pos;
1634 }
1635
1636 float scoreboard_time;
1637 bool have_weapon_stats;
1638 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1639 {
1640         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1641                 return false;
1642         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1643                 return false;
1644
1645         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1646                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1647                 && !intermission)
1648         {
1649                 return false;
1650         }
1651
1652         if (!have_weapon_stats)
1653         {
1654                 FOREACH(Weapons, it != WEP_Null, {
1655                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1656                         if (weapon_stats >= 0)
1657                         {
1658                                 have_weapon_stats = true;
1659                                 break;
1660                         }
1661                 });
1662                 if (!have_weapon_stats)
1663                         return false;
1664         }
1665
1666         return true;
1667 }
1668
1669 bool have_item_stats;
1670 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1671 {
1672         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1673                 return false;
1674         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1675                 return false;
1676
1677         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1678                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1679                 && !intermission)
1680         {
1681                 return false;
1682         }
1683
1684         if (!have_item_stats)
1685         {
1686                 IL_EACH(default_order_items, true, {
1687                         if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1688                         {
1689                                 int q = g_inventory.inv_items[it.m_id];
1690                                 //q = 1; // debug: display all items
1691                                 if (q)
1692                                 {
1693                                         have_item_stats = true;
1694                                         break;
1695                                 }
1696                         }
1697                 });
1698                 if (!have_item_stats)
1699                         return false;
1700         }
1701
1702         return true;
1703 }
1704
1705 void Scoreboard_Draw()
1706 {
1707         if(!autocvar__hud_configure)
1708         {
1709                 if(!hud_draw_maximized) return;
1710
1711                 // frametime checks allow to toggle the scoreboard even when the game is paused
1712                 if(scoreboard_active) {
1713                         if (scoreboard_fade_alpha == 0)
1714                                 scoreboard_time = time;
1715                         if(hud_configure_menu_open == 1)
1716                                 scoreboard_fade_alpha = 1;
1717                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1718                         if (scoreboard_fadeinspeed && frametime)
1719                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1720                         else
1721                                 scoreboard_fade_alpha = 1;
1722                         if(hud_fontsize_str != autocvar_hud_fontsize)
1723                         {
1724                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1725                                 Scoreboard_initFieldSizes();
1726                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1727                         }
1728                 }
1729                 else {
1730                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1731                         if (scoreboard_fadeoutspeed && frametime)
1732                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1733                         else
1734                                 scoreboard_fade_alpha = 0;
1735                 }
1736
1737                 if (!scoreboard_fade_alpha)
1738                 {
1739                         scoreboard_acc_fade_alpha = 0;
1740                         scoreboard_itemstats_fade_alpha = 0;
1741                         return;
1742                 }
1743         }
1744         else
1745                 scoreboard_fade_alpha = 0;
1746
1747         if (autocvar_hud_panel_scoreboard_dynamichud)
1748                 HUD_Scale_Enable();
1749         else
1750                 HUD_Scale_Disable();
1751
1752         if(scoreboard_fade_alpha <= 0)
1753                 return;
1754         panel_fade_alpha *= scoreboard_fade_alpha;
1755         HUD_Panel_LoadCvars();
1756
1757         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1758         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1759         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1760         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1761         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1762         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1763         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1764
1765         // don't overlap with con_notify
1766         if(!autocvar__hud_configure)
1767                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1768
1769         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1770         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1771         panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1772         panel_size.x = fixed_scoreboard_width;
1773
1774         Scoreboard_UpdatePlayerTeams();
1775
1776         float initial_pos_y = panel_pos.y;
1777         vector pos = panel_pos;
1778         entity pl, tm;
1779         string str;
1780         vector str_pos;
1781
1782         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1783
1784         // Begin of Game Info Section
1785         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1786         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1787
1788         // Game Info: Game Type
1789         str = MapInfo_Type_ToText(gametype);
1790         draw_beginBoldFont();
1791         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);
1792         draw_endBoldFont();
1793
1794         // Game Info: Game Detail
1795         float tl = STAT(TIMELIMIT);
1796         float fl = STAT(FRAGLIMIT);
1797         float ll = STAT(LEADLIMIT);
1798         float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1799         str = "";
1800         if(tl > 0)
1801                 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1802         if(!gametype.m_hidelimits)
1803         {
1804                 if(fl > 0)
1805                 {
1806                         if(tl > 0)
1807                                 str = strcat(str, "^7 / "); // delimiter
1808                         if(teamplay)
1809                         {
1810                                 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1811                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1812                                         (teamscores_label(ts_primary) == "fastest") ? "" :
1813                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1814                         }
1815                         else
1816                         {
1817                                 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1818                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1819                                         (scores_label(ps_primary) == "fastest") ? "" :
1820                                         TranslateScoresLabel(scores_label(ps_primary))));
1821                         }
1822                 }
1823                 if(ll > 0)
1824                 {
1825                         if(tl > 0 || fl > 0)
1826                         {
1827                                 // delimiter
1828                                 if (ll_and_fl && fl > 0)
1829                                         str = strcat(str, "^7 & ");
1830                                 else
1831                                         str = strcat(str, "^7 / ");
1832                         }
1833
1834                         if(teamplay)
1835                         {
1836                                 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1837                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1838                                         (teamscores_label(ts_primary) == "fastest") ? "" :
1839                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1840                         }
1841                         else
1842                         {
1843                                 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1844                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1845                                         (scores_label(ps_primary) == "fastest") ? "" :
1846                                         TranslateScoresLabel(scores_label(ps_primary))));
1847                         }
1848                 }
1849         }
1850
1851         pos.y += sb_gameinfo_type_fontsize.y;
1852         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
1853         // map name
1854         str = sprintf(_("^7Map: ^2%s"), shortmapname);
1855         drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1856         // End of Game Info Section
1857
1858         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1859         if(panel.current_panel_bg != "0")
1860                 pos.y += panel_bg_border;
1861
1862         // Draw the scoreboard
1863         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1864         if(scale <= 0)
1865                 scale = 0.25;
1866         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1867
1868         if(teamplay)
1869         {
1870                 vector panel_bg_color_save = panel_bg_color;
1871                 vector team_score_baseoffset;
1872                 vector team_size_baseoffset;
1873                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1874                 {
1875                         // put team score to the left of scoreboard (and team size to the right)
1876                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1877                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1878                         if(panel.current_panel_bg != "0")
1879                         {
1880                                 team_score_baseoffset.x -= panel_bg_border;
1881                                 team_size_baseoffset.x += panel_bg_border;
1882                         }
1883                 }
1884                 else
1885                 {
1886                         // put team score to the right of scoreboard (and team size to the left)
1887                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1888                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1889                         if(panel.current_panel_bg != "0")
1890                         {
1891                                 team_score_baseoffset.x += panel_bg_border;
1892                                 team_size_baseoffset.x -= panel_bg_border;
1893                         }
1894                 }
1895
1896                 int team_size_total = 0;
1897                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1898                 {
1899                         // calculate team size total (sum of all team sizes)
1900                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1901                                 if(tm.team != NUM_SPECTATOR)
1902                                         team_size_total += tm.team_size;
1903                 }
1904
1905                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1906                 {
1907                         if(tm.team == NUM_SPECTATOR)
1908                                 continue;
1909                         if(!tm.team)
1910                                 continue;
1911
1912                         draw_beginBoldFont();
1913                         vector rgb = Team_ColorRGB(tm.team);
1914                         str = ftos(tm.(teamscores(ts_primary)));
1915                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1916                         {
1917                                 // team score on the left (default)
1918                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1919                         }
1920                         else
1921                         {
1922                                 // team score on the right
1923                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1924                         }
1925                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1926
1927                         // team size (if set to show on the side)
1928                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1929                         {
1930                                 // calculate the starting position for the whole team size info string
1931                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
1932                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1933                                 {
1934                                         // team size on the left
1935                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1936                                 }
1937                                 else
1938                                 {
1939                                         // team size on the right
1940                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1941                                 }
1942                                 str = sprintf("%d", tm.team_size);
1943                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1944                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1945                                 str = sprintf("/%d", team_size_total);
1946                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1947                         }
1948
1949
1950                         // secondary score, e.g. keyhunt
1951                         if(ts_primary != ts_secondary)
1952                         {
1953                                 str = ftos(tm.(teamscores(ts_secondary)));
1954                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1955                                 {
1956                                         // left
1957                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1958                                 }
1959                                 else
1960                                 {
1961                                         // right
1962                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1963                                 }
1964
1965                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1966                         }
1967                         draw_endBoldFont();
1968                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1969                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1970                         else if(panel_bg_color_team > 0)
1971                                 panel_bg_color = rgb * panel_bg_color_team;
1972                         else
1973                                 panel_bg_color = rgb;
1974                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1975                 }
1976                 panel_bg_color = panel_bg_color_save;
1977         }
1978         else
1979         {
1980                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1981                         if(tm.team != NUM_SPECTATOR)
1982                                 break;
1983
1984                 // display it anyway
1985                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1986         }
1987
1988         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1989                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1990         if (Scoreboard_ItemStats_WouldDraw(pos.y))
1991                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1992
1993         if(MUTATOR_CALLHOOK(ShowRankings)) {
1994                 string ranktitle = M_ARGV(0, string);
1995                 if(race_speedaward) {
1996                         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);
1997                         pos.y += 1.25 * hud_fontsize.y;
1998                 }
1999                 if(race_speedaward_alltimebest) {
2000                         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);
2001                         pos.y += 1.25 * hud_fontsize.y;
2002                 }
2003                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2004         }
2005
2006         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2007
2008         // List spectators
2009         for(pl = players.sort_next; pl; pl = pl.sort_next)
2010         {
2011                 if(pl.team == NUM_SPECTATOR)
2012                 {
2013                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2014                                 if(tm.team == NUM_SPECTATOR)
2015                                         break;
2016                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2017                         draw_beginBoldFont();
2018                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2019                         draw_endBoldFont();
2020                         pos.y += 1.25 * hud_fontsize.y;
2021
2022                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2023                         pos.y += 1.25 * hud_fontsize.y;
2024
2025                         break;
2026                 }
2027         }
2028
2029
2030         // print information about respawn status
2031         float respawn_time = STAT(RESPAWN_TIME);
2032         if(!intermission)
2033         if(respawn_time)
2034         {
2035                 if(respawn_time < 0)
2036                 {
2037                         // a negative number means we are awaiting respawn, time value is still the same
2038                         respawn_time *= -1; // remove mark now that we checked it
2039
2040                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2041                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2042                         else
2043                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2044                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2045                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2046                                                 :
2047                                                 count_seconds(ceil(respawn_time - time))
2048                                         )
2049                                 );
2050                 }
2051                 else if(time < respawn_time)
2052                 {
2053                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2054                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2055                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2056                                         :
2057                                         count_seconds(ceil(respawn_time - time))
2058                                 )
2059                         );
2060                 }
2061                 else if(time >= respawn_time)
2062                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2063
2064                 pos.y += 1.2 * hud_fontsize.y;
2065                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2066         }
2067
2068         pos.y += 2 * hud_fontsize.y;
2069         if (scoreboard_fade_alpha < 1)
2070                 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2071         else if (pos.y != scoreboard_bottom)
2072         {
2073                 if (pos.y > scoreboard_bottom)
2074                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2075                 else
2076                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));
2077         }
2078 }