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