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