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