]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
f3e198bf8a74848405e4e32d32012a1de245accc
[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         {
760                 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
761                 Scoreboard_FixColumnWidth(i, "");
762         }
763 }
764
765 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
766 {
767         int i;
768         vector column_dim = eY * panel_size.y;
769         if(other_players)
770                 column_dim.y -= 1.25 * hud_fontsize.y;
771         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
772         pos.x += hud_fontsize.x * 0.5;
773         for(i = 0; i < sbt_num_fields; ++i)
774         {
775                 if(sbt_field[i] == SP_SEPARATOR)
776                         break;
777                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
778                 if (sbt_highlight)
779                         if (i % 2)
780                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
781                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
782                 pos.x += column_dim.x;
783         }
784         if(sbt_field[i] == SP_SEPARATOR)
785         {
786                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
787                 for(i = sbt_num_fields - 1; i > 0; --i)
788                 {
789                         if(sbt_field[i] == SP_SEPARATOR)
790                                 break;
791
792                         pos.x -= sbt_field_size[i];
793
794                         if (sbt_highlight)
795                                 if (!(i % 2))
796                                 {
797                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
798                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
799                                 }
800
801                         text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
802                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
803                         pos.x -= hud_fontsize.x;
804                 }
805         }
806
807         pos.x = panel_pos.x;
808         pos.y += 1.25 * hud_fontsize.y;
809         return pos;
810 }
811
812 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
813 {
814     TC(bool, is_self); TC(int, pl_number);
815         string str;
816         bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
817
818         vector h_pos = item_pos;
819         vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
820         // alternated rows highlighting
821         if(is_self)
822                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
823         else if((sbt_highlight) && (!(pl_number % 2)))
824                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
825
826         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
827
828         vector pos = item_pos;
829         pos.x += hud_fontsize.x * 0.5;
830         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
831         vector tmp = '0 0 0';
832         int i;
833         PlayerScoreField field;
834         for(i = 0; i < sbt_num_fields; ++i)
835         {
836                 field = sbt_field[i];
837                 if(field == SP_SEPARATOR)
838                         break;
839
840                 if(is_spec && field != SP_NAME && field != SP_PING) {
841                         pos.x += sbt_field_size[i] + hud_fontsize.x;
842                         continue;
843                 }
844                 str = Scoreboard_GetField(pl, field);
845                 str = Scoreboard_FixColumnWidth(i, str);
846
847                 pos.x += sbt_field_size[i] + hud_fontsize.x;
848
849                 if(field == SP_NAME) {
850                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
851                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
852                 } else {
853                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
854                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
855                 }
856
857                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
858                 if(sbt_field_icon0 != "")
859                         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);
860                 if(sbt_field_icon1 != "")
861                         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);
862                 if(sbt_field_icon2 != "")
863                         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);
864         }
865
866         if(sbt_field[i] == SP_SEPARATOR)
867         {
868                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
869                 for(i = sbt_num_fields-1; i > 0; --i)
870                 {
871                         field = sbt_field[i];
872                         if(field == SP_SEPARATOR)
873                                 break;
874
875                         if(is_spec && field != SP_NAME && field != SP_PING) {
876                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
877                                 continue;
878                         }
879
880                         str = Scoreboard_GetField(pl, field);
881                         str = Scoreboard_FixColumnWidth(i, str);
882
883                         if(field == SP_NAME) {
884                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
885                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
886                         } else {
887                                 tmp.x = sbt_fixcolumnwidth_len;
888                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
889                         }
890
891                         tmp.x = sbt_field_size[i];
892                         if(sbt_field_icon0 != "")
893                                 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);
894                         if(sbt_field_icon1 != "")
895                                 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);
896                         if(sbt_field_icon2 != "")
897                                 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);
898                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
899                 }
900         }
901
902         if(pl.eliminated)
903                 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
904 }
905
906 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
907 {
908         int i = 0;
909         vector h_pos = item_pos;
910         vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
911
912         bool complete = (this_team == NUM_SPECTATOR);
913
914         if(!complete)
915         if((sbt_highlight) && (!(pl_number % 2)))
916                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
917
918         vector pos = item_pos;
919         pos.x += hud_fontsize.x * 0.5;
920         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
921
922         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
923         if(!complete)
924                 width_limit -= stringwidth("...", false, hud_fontsize);
925         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
926         string field = "";
927         float fieldsize = 0;
928         float min_fieldsize = 0;
929         float fieldpadding = hud_fontsize.x * 0.25;
930         if(this_team == NUM_SPECTATOR)
931         {
932                 if(autocvar_hud_panel_scoreboard_spectators_showping)
933                         min_fieldsize = stringwidth("999", false, hud_fontsize);
934         }
935         else if(autocvar_hud_panel_scoreboard_others_showscore)
936                 min_fieldsize = stringwidth("99", false, hud_fontsize);
937         for(i = 0; pl; pl = pl.sort_next)
938         {
939                 if(pl.team != this_team)
940                         continue;
941                 if(pl == ignored_pl)
942                         continue;
943
944                 field = "";
945                 if(this_team == NUM_SPECTATOR)
946                 {
947                         if(autocvar_hud_panel_scoreboard_spectators_showping)
948                                 field = Scoreboard_GetField(pl, SP_PING);
949                 }
950                 else if(autocvar_hud_panel_scoreboard_others_showscore)
951                         field = ftos(pl.(scores(ps_primary)));
952
953                 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
954                 float str_width = stringwidth(str, true, hud_fontsize);
955                 if(field != "")
956                 {
957                         fieldsize = stringwidth(field, false, hud_fontsize);
958                         str_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
959                 }
960
961                 if(pos.x + str_width > width_limit)
962                 {
963                         ++i;
964                         if(!complete)
965                         {
966                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
967                                 break;
968                         }
969                         else
970                         {
971                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
972                                 pos.y += hud_fontsize.y * 1.25;
973                         }
974                 }
975                 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
976                 pos.x += str_width;
977                 if(field != "")
978                 {
979                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
980                         h_size.y = hud_fontsize.y;
981                         pos.x -= h_size.x;
982                         if(sbt_highlight)
983                                 drawfill(pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
984                         drawstring(pos + eX * (fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5), field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
985                         pos.x += h_size.x;
986                 }
987                 pos.x += hud_fontsize.x;
988         }
989         return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
990 }
991
992 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
993 {
994         int max_players = 999;
995         if(autocvar_hud_panel_scoreboard_maxheight > 0)
996         {
997                 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
998                 if(teamplay)
999                         max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
1000                 max_players = floor(max_players / (hud_fontsize.y * 1.25));
1001                 if(max_players <= 1)
1002                         max_players = 1;
1003                 if(max_players == tm.team_size)
1004                         max_players = 999;
1005         }
1006
1007         entity pl;
1008         entity me = playerslots[current_player];
1009         panel_pos = pos;
1010         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1011         panel_size.y += panel_bg_padding * 2;
1012         HUD_Panel_DrawBg();
1013
1014         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1015         if(panel.current_panel_bg != "0")
1016                 end_pos.y += panel_bg_border * 2;
1017
1018         if(panel_bg_padding)
1019         {
1020                 panel_pos += '1 1 0' * panel_bg_padding;
1021                 panel_size -= '2 2 0' * panel_bg_padding;
1022         }
1023
1024         pos = panel_pos;
1025         vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1026
1027         // rounded header
1028         if (sbt_bg_alpha)
1029                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1030
1031         pos.y += 1.25 * hud_fontsize.y;
1032
1033         // table background
1034         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1035         if (sbt_bg_alpha)
1036                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1037
1038
1039         // print header row and highlight columns
1040         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1041
1042         // fill the table and draw the rows
1043         bool is_self = false;
1044         bool self_shown = false;
1045         int i = 0;
1046         for(pl = players.sort_next; pl; pl = pl.sort_next)
1047         {
1048                 if(pl.team != tm.team)
1049                         continue;
1050                 if(i == max_players - 2 && pl != me)
1051                 {
1052                         if(!self_shown && me.team == tm.team)
1053                         {
1054                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1055                                 self_shown = true;
1056                                 pos.y += 1.25 * hud_fontsize.y;
1057                                 ++i;
1058                         }
1059                 }
1060                 if(i >= max_players - 1)
1061                 {
1062                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1063                         break;
1064                 }
1065                 is_self = (pl.sv_entnum == current_player);
1066                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1067                 if(is_self)
1068                         self_shown = true;
1069                 pos.y += 1.25 * hud_fontsize.y;
1070                 ++i;
1071         }
1072
1073         panel_size.x += panel_bg_padding * 2; // restore initial width
1074         return end_pos;
1075 }
1076
1077 bool Scoreboard_WouldDraw()
1078 {
1079         if (QuickMenu_IsOpened())
1080                 return false;
1081         else if (HUD_Radar_Clickable())
1082                 return false;
1083         else if (scoreboard_showscores)
1084                 return true;
1085         else if (intermission == 1)
1086                 return true;
1087         else if (intermission == 2)
1088                 return false;
1089         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1090                 return true;
1091         else if (scoreboard_showscores_force)
1092                 return true;
1093         return false;
1094 }
1095
1096 float average_accuracy;
1097 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1098 {
1099         WepSet weapons_stat = WepSet_GetFromStat();
1100         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1101         int disownedcnt = 0;
1102         int nHidden = 0;
1103         FOREACH(Weapons, it != WEP_Null, {
1104                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1105
1106                 WepSet set = it.m_wepset;
1107                 if (weapon_stats < 0)
1108                 {
1109                         if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1110                                 nHidden += 1;
1111                         else if (!(weapons_stat & set || weapons_inmap & set))
1112                                 ++disownedcnt;
1113                 }
1114         });
1115
1116         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1117         if (weapon_cnt <= 0) return pos;
1118
1119         int rows = 1;
1120         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1121                 rows = 2;
1122         int columnns = ceil(weapon_cnt / rows);
1123
1124         float weapon_height = 29;
1125         float height = hud_fontsize.y + weapon_height;
1126
1127         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1128         pos.y += 1.25 * hud_fontsize.y;
1129         if(panel.current_panel_bg != "0")
1130                 pos.y += panel_bg_border;
1131
1132         panel_pos = pos;
1133         panel_size.y = height * rows;
1134         panel_size.y += panel_bg_padding * 2;
1135         HUD_Panel_DrawBg();
1136
1137         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1138         if(panel.current_panel_bg != "0")
1139                 end_pos.y += panel_bg_border * 2;
1140
1141         if(panel_bg_padding)
1142         {
1143                 panel_pos += '1 1 0' * panel_bg_padding;
1144                 panel_size -= '2 2 0' * panel_bg_padding;
1145         }
1146
1147         pos = panel_pos;
1148         vector tmp = panel_size;
1149
1150         float weapon_width = tmp.x / columnns / rows;
1151
1152         if (sbt_bg_alpha)
1153                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1154
1155         if(sbt_highlight)
1156         {
1157                 // column highlighting
1158                 for (int i = 0; i < columnns; ++i)
1159                         if ((i % 2) == 0)
1160                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1161
1162                 // row highlighting
1163                 for (int i = 0; i < rows; ++i)
1164                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1165         }
1166
1167         average_accuracy = 0;
1168         int weapons_with_stats = 0;
1169         if (rows == 2)
1170                 pos.x += weapon_width / 2;
1171
1172         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1173                 rgb = '1 1 1';
1174         else
1175                 Accuracy_LoadColors();
1176
1177         float oldposx = pos.x;
1178         vector tmpos = pos;
1179
1180         int column = 0;
1181         FOREACH(Weapons, it != WEP_Null, {
1182                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1183
1184                 WepSet set = it.m_wepset;
1185                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1186                         continue;
1187
1188                 float weapon_alpha;
1189                 if (weapon_stats >= 0)
1190                         weapon_alpha = sbt_fg_alpha;
1191                 else
1192                         weapon_alpha = 0.2 * sbt_fg_alpha;
1193
1194                 // weapon icon
1195                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1196                 // the accuracy
1197                 if (weapon_stats >= 0) {
1198                         weapons_with_stats += 1;
1199                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1200
1201                         string s;
1202                         s = sprintf("%d%%", weapon_stats * 100);
1203
1204                         float padding;
1205                         padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1206
1207                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1208                                 rgb = Accuracy_GetColor(weapon_stats);
1209
1210                         drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1211                 }
1212                 tmpos.x += weapon_width * rows;
1213                 pos.x += weapon_width * rows;
1214                 if (rows == 2 && column == columnns - 1) {
1215                         tmpos.x = oldposx;
1216                         tmpos.y += height;
1217                         pos.y += height;
1218                 }
1219                 ++column;
1220         });
1221
1222         if (weapons_with_stats)
1223                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1224
1225         panel_size.x += panel_bg_padding * 2; // restore initial width
1226         return end_pos;
1227 }
1228
1229 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1230         float px = pos.x;
1231         pos.x += hud_fontsize.x * 0.25;
1232         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1233         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1234         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1235         pos.x = px;
1236         pos.y += hud_fontsize.y;
1237
1238         return pos;
1239 }
1240
1241 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1242         float stat_secrets_found, stat_secrets_total;
1243         float stat_monsters_killed, stat_monsters_total;
1244         float rows = 0;
1245         string val;
1246
1247         // get monster stats
1248         stat_monsters_killed = STAT(MONSTERS_KILLED);
1249         stat_monsters_total = STAT(MONSTERS_TOTAL);
1250
1251         // get secrets stats
1252         stat_secrets_found = STAT(SECRETS_FOUND);
1253         stat_secrets_total = STAT(SECRETS_TOTAL);
1254
1255         // get number of rows
1256         if(stat_secrets_total)
1257                 rows += 1;
1258         if(stat_monsters_total)
1259                 rows += 1;
1260
1261         // if no rows, return
1262         if (!rows)
1263                 return pos;
1264
1265         //  draw table header
1266         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1267         pos.y += 1.25 * hud_fontsize.y;
1268         if(panel.current_panel_bg != "0")
1269                 pos.y += panel_bg_border;
1270
1271         panel_pos = pos;
1272         panel_size.y = hud_fontsize.y * rows;
1273         panel_size.y += panel_bg_padding * 2;
1274         HUD_Panel_DrawBg();
1275
1276         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1277         if(panel.current_panel_bg != "0")
1278                 end_pos.y += panel_bg_border * 2;
1279
1280         if(panel_bg_padding)
1281         {
1282                 panel_pos += '1 1 0' * panel_bg_padding;
1283                 panel_size -= '2 2 0' * panel_bg_padding;
1284         }
1285
1286         pos = panel_pos;
1287         vector tmp = panel_size;
1288
1289         if (sbt_bg_alpha)
1290                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1291
1292         // draw monsters
1293         if(stat_monsters_total)
1294         {
1295                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1296                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1297         }
1298
1299         // draw secrets
1300         if(stat_secrets_total)
1301         {
1302                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1303                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1304         }
1305
1306         panel_size.x += panel_bg_padding * 2; // restore initial width
1307         return end_pos;
1308 }
1309
1310
1311 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1312 {
1313         int i;
1314         RANKINGS_RECEIVED_CNT = 0;
1315         for (i=RANKINGS_CNT-1; i>=0; --i)
1316                 if (grecordtime[i])
1317                         ++RANKINGS_RECEIVED_CNT;
1318
1319         if (RANKINGS_RECEIVED_CNT == 0)
1320                 return pos;
1321
1322         vector hl_rgb = rgb + '0.5 0.5 0.5';
1323
1324         pos.y += hud_fontsize.y;
1325         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1326         pos.y += 1.25 * hud_fontsize.y;
1327         if(panel.current_panel_bg != "0")
1328                 pos.y += panel_bg_border;
1329
1330         panel_pos = pos;
1331
1332         float namesize = 0;
1333         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1334         {
1335                 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1336                 if(f > namesize)
1337                         namesize = f;
1338         }
1339         bool cut = false;
1340         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1341         {
1342                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1343                 cut = true;
1344         }
1345
1346         float ranksize = 3 * hud_fontsize.x;
1347         float timesize = 5 * hud_fontsize.x;
1348         vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1349         int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1350         columns = min(columns, RANKINGS_RECEIVED_CNT);
1351
1352         // expand name column to fill the entire row
1353         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1354         namesize += available_space;
1355         columnsize.x += available_space;
1356
1357         panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1358         panel_size.y += panel_bg_padding * 2;
1359
1360         HUD_Panel_DrawBg();
1361
1362         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1363         if(panel.current_panel_bg != "0")
1364                 end_pos.y += panel_bg_border * 2;
1365
1366         if(panel_bg_padding)
1367         {
1368                 panel_pos += '1 1 0' * panel_bg_padding;
1369                 panel_size -= '2 2 0' * panel_bg_padding;
1370         }
1371
1372         pos = panel_pos;
1373
1374         if (sbt_bg_alpha)
1375                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1376
1377         vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1378         string str = "";
1379         int column = 0, j = 0;
1380         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1381         {
1382                 float t;
1383                 t = grecordtime[i];
1384                 if (t == 0)
1385                         continue;
1386
1387                 if(grecordholder[i] == entcs_GetName(player_localnum))
1388                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1389                 else if(!((j + column) & 1) && sbt_highlight)
1390                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1391
1392                 str = count_ordinal(i+1);
1393                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1394                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1395                 str = grecordholder[i];
1396                 if(cut)
1397                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1398                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1399
1400                 pos.y += 1.25 * hud_fontsize.y;
1401                 j++;
1402                 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1403                 {
1404                         column++;
1405                         j = 0;
1406                         pos.x += panel_size.x / columns;
1407                         pos.y = panel_pos.y;
1408                 }
1409         }
1410
1411         panel_size.x += panel_bg_padding * 2; // restore initial width
1412         return end_pos;
1413 }
1414
1415 void Scoreboard_Draw()
1416 {
1417         if(!autocvar__hud_configure)
1418         {
1419                 if(!hud_draw_maximized) return;
1420
1421                 // frametime checks allow to toggle the scoreboard even when the game is paused
1422                 if(scoreboard_active) {
1423                         if(hud_configure_menu_open == 1)
1424                                 scoreboard_fade_alpha = 1;
1425                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1426                         if (scoreboard_fadeinspeed && frametime)
1427                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1428                         else
1429                                 scoreboard_fade_alpha = 1;
1430                         if(hud_fontsize_str != autocvar_hud_fontsize)
1431                         {
1432                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1433                                 Scoreboard_initFieldSizes();
1434                                 if(hud_fontsize_str)
1435                                         strunzone(hud_fontsize_str);
1436                                 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1437                         }
1438                 }
1439                 else {
1440                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1441                         if (scoreboard_fadeoutspeed && frametime)
1442                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1443                         else
1444                                 scoreboard_fade_alpha = 0;
1445                 }
1446
1447                 if (!scoreboard_fade_alpha)
1448                         return;
1449         }
1450         else
1451                 scoreboard_fade_alpha = 0;
1452
1453         if (autocvar_hud_panel_scoreboard_dynamichud)
1454                 HUD_Scale_Enable();
1455         else
1456                 HUD_Scale_Disable();
1457
1458         if(scoreboard_fade_alpha <= 0)
1459                 return;
1460         panel_fade_alpha *= scoreboard_fade_alpha;
1461         HUD_Panel_LoadCvars();
1462
1463         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1464         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1465         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1466         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1467         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1468         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1469
1470         // don't overlap with con_notify
1471         if(!autocvar__hud_configure)
1472                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1473
1474         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1475         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1476         panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1477         panel_size.x = fixed_scoreboard_width;
1478
1479         Scoreboard_UpdatePlayerTeams();
1480
1481         vector pos = panel_pos;
1482         entity pl, tm;
1483         string str;
1484
1485         // Heading
1486         vector sb_heading_fontsize;
1487         sb_heading_fontsize = hud_fontsize * 2;
1488         draw_beginBoldFont();
1489         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1490         draw_endBoldFont();
1491
1492         pos.y += sb_heading_fontsize.y;
1493         if(panel.current_panel_bg != "0")
1494                 pos.y += panel_bg_border;
1495
1496         // Draw the scoreboard
1497         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1498         if(scale <= 0)
1499                 scale = 0.25;
1500         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1501
1502         if(teamplay)
1503         {
1504                 vector panel_bg_color_save = panel_bg_color;
1505                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1506                 if(panel.current_panel_bg != "0")
1507                         team_score_baseoffset.x -= panel_bg_border;
1508                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1509                 {
1510                         if(tm.team == NUM_SPECTATOR)
1511                                 continue;
1512                         if(!tm.team)
1513                                 continue;
1514
1515                         draw_beginBoldFont();
1516                         vector rgb = Team_ColorRGB(tm.team);
1517                         str = ftos(tm.(teamscores(ts_primary)));
1518                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1519
1520                         if(ts_primary != ts_secondary)
1521                         {
1522                                 str = ftos(tm.(teamscores(ts_secondary)));
1523                                 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);
1524                         }
1525                         draw_endBoldFont();
1526                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1527                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1528                         else if(panel_bg_color_team > 0)
1529                                 panel_bg_color = rgb * panel_bg_color_team;
1530                         else
1531                                 panel_bg_color = rgb;
1532                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1533                 }
1534                 panel_bg_color = panel_bg_color_save;
1535         }
1536         else
1537         {
1538                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1539                         if(tm.team != NUM_SPECTATOR)
1540                                 break;
1541                 // display it anyway
1542                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1543         }
1544
1545         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1546                 if(race_speedaward) {
1547                         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);
1548                         pos.y += 1.25 * hud_fontsize.y;
1549                 }
1550                 if(race_speedaward_alltimebest) {
1551                         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);
1552                         pos.y += 1.25 * hud_fontsize.y;
1553                 }
1554                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1555         }
1556         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1557                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1558
1559         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1560
1561         // List spectators
1562         for(pl = players.sort_next; pl; pl = pl.sort_next)
1563         {
1564                 if(pl.team == NUM_SPECTATOR)
1565                 {
1566                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1567                                 if(tm.team == NUM_SPECTATOR)
1568                                         break;
1569                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1570                         draw_beginBoldFont();
1571                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1572                         draw_endBoldFont();
1573                         pos.y += 1.25 * hud_fontsize.y;
1574
1575                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1576                         pos.y += 1.25 * hud_fontsize.y;
1577
1578                         break;
1579                 }
1580         }
1581
1582         // Print info string
1583         float tl, fl, ll;
1584         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1585         tl = STAT(TIMELIMIT);
1586         fl = STAT(FRAGLIMIT);
1587         ll = STAT(LEADLIMIT);
1588         if(gametype == MAPINFO_TYPE_LMS)
1589         {
1590                 if(tl > 0)
1591                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1592         }
1593         else
1594         {
1595                 if(tl > 0)
1596                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1597                 if(fl > 0)
1598                 {
1599                         if(tl > 0)
1600                                 str = strcat(str, _(" or"));
1601                         if(teamplay)
1602                         {
1603                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1604                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1605                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1606                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1607                         }
1608                         else
1609                         {
1610                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1611                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1612                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1613                                         TranslateScoresLabel(scores_label(ps_primary))));
1614                         }
1615                 }
1616                 if(ll > 0)
1617                 {
1618                         if(tl > 0 || fl > 0)
1619                                 str = strcat(str, _(" or"));
1620                         if(teamplay)
1621                         {
1622                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1623                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1624                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1625                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1626                         }
1627                         else
1628                         {
1629                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1630                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1631                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1632                                         TranslateScoresLabel(scores_label(ps_primary))));
1633                         }
1634                 }
1635         }
1636
1637         pos.y += 1.2 * hud_fontsize.y;
1638         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1639
1640         // print information about respawn status
1641         float respawn_time = STAT(RESPAWN_TIME);
1642         if(!intermission)
1643         if(respawn_time)
1644         {
1645                 if(respawn_time < 0)
1646                 {
1647                         // a negative number means we are awaiting respawn, time value is still the same
1648                         respawn_time *= -1; // remove mark now that we checked it
1649
1650                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1651                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1652                         else
1653                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1654                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1655                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1656                                                 :
1657                                                 count_seconds(ceil(respawn_time - time))
1658                                         )
1659                                 );
1660                 }
1661                 else if(time < respawn_time)
1662                 {
1663                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1664                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1665                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1666                                         :
1667                                         count_seconds(ceil(respawn_time - time))
1668                                 )
1669                         );
1670                 }
1671                 else if(time >= respawn_time)
1672                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1673
1674                 pos.y += 1.2 * hud_fontsize.y;
1675                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1676         }
1677
1678         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1679 }