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