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