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