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