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