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