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