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