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