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