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