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