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