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