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