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