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