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