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