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