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