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