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