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