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