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