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