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