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