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