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