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