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