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