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