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