]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Merge branch 'martin-t/damagetext' 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 (MUTATOR_CALLHOOK(DrawScoreboard))
1094                 return false;
1095         else if (QuickMenu_IsOpened())
1096                 return false;
1097         else if (HUD_Radar_Clickable())
1098                 return false;
1099         else if (scoreboard_showscores)
1100                 return true;
1101         else if (intermission == 1)
1102                 return true;
1103         else if (intermission == 2)
1104                 return false;
1105         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1106                 return true;
1107         else if (scoreboard_showscores_force)
1108                 return true;
1109         return false;
1110 }
1111
1112 float average_accuracy;
1113 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1114 {
1115         WepSet weapons_stat = WepSet_GetFromStat();
1116         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1117         int disownedcnt = 0;
1118         int nHidden = 0;
1119         FOREACH(Weapons, it != WEP_Null, {
1120                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1121
1122                 WepSet set = it.m_wepset;
1123                 if (weapon_stats < 0)
1124                 {
1125                         if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1126                                 nHidden += 1;
1127                         else if (!(weapons_stat & set || weapons_inmap & set))
1128                                 ++disownedcnt;
1129                 }
1130         });
1131
1132         int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1133         if (weapon_cnt <= 0) return pos;
1134
1135         int rows = 1;
1136         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1137                 rows = 2;
1138         int columnns = ceil(weapon_cnt / rows);
1139
1140         float weapon_height = 29;
1141         float height = hud_fontsize.y + weapon_height;
1142
1143         drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1144         pos.y += 1.25 * hud_fontsize.y;
1145         if(panel.current_panel_bg != "0")
1146                 pos.y += panel_bg_border;
1147
1148         panel_pos = pos;
1149         panel_size.y = height * rows;
1150         panel_size.y += panel_bg_padding * 2;
1151         HUD_Panel_DrawBg();
1152
1153         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1154         if(panel.current_panel_bg != "0")
1155                 end_pos.y += panel_bg_border * 2;
1156
1157         if(panel_bg_padding)
1158         {
1159                 panel_pos += '1 1 0' * panel_bg_padding;
1160                 panel_size -= '2 2 0' * panel_bg_padding;
1161         }
1162
1163         pos = panel_pos;
1164         vector tmp = panel_size;
1165
1166         float weapon_width = tmp.x / columnns / rows;
1167
1168         if (sbt_bg_alpha)
1169                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1170
1171         if(sbt_highlight)
1172         {
1173                 // column highlighting
1174                 for (int i = 0; i < columnns; ++i)
1175                         if ((i % 2) == 0)
1176                                 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1177
1178                 // row highlighting
1179                 for (int i = 0; i < rows; ++i)
1180                         drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1181         }
1182
1183         average_accuracy = 0;
1184         int weapons_with_stats = 0;
1185         if (rows == 2)
1186                 pos.x += weapon_width / 2;
1187
1188         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1189                 rgb = '1 1 1';
1190         else
1191                 Accuracy_LoadColors();
1192
1193         float oldposx = pos.x;
1194         vector tmpos = pos;
1195
1196         int column = 0;
1197         FOREACH(Weapons, it != WEP_Null, {
1198                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1199
1200                 WepSet set = it.m_wepset;
1201                 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1202                         continue;
1203
1204                 float weapon_alpha;
1205                 if (weapon_stats >= 0)
1206                         weapon_alpha = sbt_fg_alpha;
1207                 else
1208                         weapon_alpha = 0.2 * sbt_fg_alpha;
1209
1210                 // weapon icon
1211                 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1212                 // the accuracy
1213                 if (weapon_stats >= 0) {
1214                         weapons_with_stats += 1;
1215                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1216
1217                         string s;
1218                         s = sprintf("%d%%", weapon_stats * 100);
1219
1220                         float padding;
1221                         padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1222
1223                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1224                                 rgb = Accuracy_GetColor(weapon_stats);
1225
1226                         drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1227                 }
1228                 tmpos.x += weapon_width * rows;
1229                 pos.x += weapon_width * rows;
1230                 if (rows == 2 && column == columnns - 1) {
1231                         tmpos.x = oldposx;
1232                         tmpos.y += height;
1233                         pos.y += height;
1234                 }
1235                 ++column;
1236         });
1237
1238         if (weapons_with_stats)
1239                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1240
1241         panel_size.x += panel_bg_padding * 2; // restore initial width
1242         return end_pos;
1243 }
1244
1245 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1246         float px = pos.x;
1247         pos.x += hud_fontsize.x * 0.25;
1248         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1249         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1250         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1251         pos.x = px;
1252         pos.y += hud_fontsize.y;
1253
1254         return pos;
1255 }
1256
1257 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1258         float stat_secrets_found, stat_secrets_total;
1259         float stat_monsters_killed, stat_monsters_total;
1260         float rows = 0;
1261         string val;
1262
1263         // get monster stats
1264         stat_monsters_killed = STAT(MONSTERS_KILLED);
1265         stat_monsters_total = STAT(MONSTERS_TOTAL);
1266
1267         // get secrets stats
1268         stat_secrets_found = STAT(SECRETS_FOUND);
1269         stat_secrets_total = STAT(SECRETS_TOTAL);
1270
1271         // get number of rows
1272         if(stat_secrets_total)
1273                 rows += 1;
1274         if(stat_monsters_total)
1275                 rows += 1;
1276
1277         // if no rows, return
1278         if (!rows)
1279                 return pos;
1280
1281         //  draw table header
1282         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1283         pos.y += 1.25 * hud_fontsize.y;
1284         if(panel.current_panel_bg != "0")
1285                 pos.y += panel_bg_border;
1286
1287         panel_pos = pos;
1288         panel_size.y = hud_fontsize.y * rows;
1289         panel_size.y += panel_bg_padding * 2;
1290         HUD_Panel_DrawBg();
1291
1292         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1293         if(panel.current_panel_bg != "0")
1294                 end_pos.y += panel_bg_border * 2;
1295
1296         if(panel_bg_padding)
1297         {
1298                 panel_pos += '1 1 0' * panel_bg_padding;
1299                 panel_size -= '2 2 0' * panel_bg_padding;
1300         }
1301
1302         pos = panel_pos;
1303         vector tmp = panel_size;
1304
1305         if (sbt_bg_alpha)
1306                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1307
1308         // draw monsters
1309         if(stat_monsters_total)
1310         {
1311                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1312                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1313         }
1314
1315         // draw secrets
1316         if(stat_secrets_total)
1317         {
1318                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1319                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1320         }
1321
1322         panel_size.x += panel_bg_padding * 2; // restore initial width
1323         return end_pos;
1324 }
1325
1326
1327 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1328 {
1329         int i;
1330         RANKINGS_RECEIVED_CNT = 0;
1331         for (i=RANKINGS_CNT-1; i>=0; --i)
1332                 if (grecordtime[i])
1333                         ++RANKINGS_RECEIVED_CNT;
1334
1335         if (RANKINGS_RECEIVED_CNT == 0)
1336                 return pos;
1337
1338         vector hl_rgb = rgb + '0.5 0.5 0.5';
1339
1340         pos.y += hud_fontsize.y;
1341         drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1342         pos.y += 1.25 * hud_fontsize.y;
1343         if(panel.current_panel_bg != "0")
1344                 pos.y += panel_bg_border;
1345
1346         panel_pos = pos;
1347
1348         float namesize = 0;
1349         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1350         {
1351                 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1352                 if(f > namesize)
1353                         namesize = f;
1354         }
1355         bool cut = false;
1356         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1357         {
1358                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1359                 cut = true;
1360         }
1361
1362         float ranksize = 3 * hud_fontsize.x;
1363         float timesize = 5 * hud_fontsize.x;
1364         vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1365         int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1366         columns = min(columns, RANKINGS_RECEIVED_CNT);
1367
1368         // expand name column to fill the entire row
1369         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1370         namesize += available_space;
1371         columnsize.x += available_space;
1372
1373         panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1374         panel_size.y += panel_bg_padding * 2;
1375
1376         HUD_Panel_DrawBg();
1377
1378         vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1379         if(panel.current_panel_bg != "0")
1380                 end_pos.y += panel_bg_border * 2;
1381
1382         if(panel_bg_padding)
1383         {
1384                 panel_pos += '1 1 0' * panel_bg_padding;
1385                 panel_size -= '2 2 0' * panel_bg_padding;
1386         }
1387
1388         pos = panel_pos;
1389
1390         if (sbt_bg_alpha)
1391                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1392
1393         vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1394         string str = "";
1395         int column = 0, j = 0;
1396         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1397         {
1398                 float t;
1399                 t = grecordtime[i];
1400                 if (t == 0)
1401                         continue;
1402
1403                 if(grecordholder[i] == entcs_GetName(player_localnum))
1404                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1405                 else if(!((j + column) & 1) && sbt_highlight)
1406                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1407
1408                 str = count_ordinal(i+1);
1409                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1410                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1411                 str = grecordholder[i];
1412                 if(cut)
1413                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1414                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1415
1416                 pos.y += 1.25 * hud_fontsize.y;
1417                 j++;
1418                 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1419                 {
1420                         column++;
1421                         j = 0;
1422                         pos.x += panel_size.x / columns;
1423                         pos.y = panel_pos.y;
1424                 }
1425         }
1426
1427         panel_size.x += panel_bg_padding * 2; // restore initial width
1428         return end_pos;
1429 }
1430
1431 void Scoreboard_Draw()
1432 {
1433         if(!autocvar__hud_configure)
1434         {
1435                 if(!hud_draw_maximized) return;
1436
1437                 // frametime checks allow to toggle the scoreboard even when the game is paused
1438                 if(scoreboard_active) {
1439                         if(hud_configure_menu_open == 1)
1440                                 scoreboard_fade_alpha = 1;
1441                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1442                         if (scoreboard_fadeinspeed && frametime)
1443                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1444                         else
1445                                 scoreboard_fade_alpha = 1;
1446                         if(hud_fontsize_str != autocvar_hud_fontsize)
1447                         {
1448                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1449                                 Scoreboard_initFieldSizes();
1450                                 if(hud_fontsize_str)
1451                                         strunzone(hud_fontsize_str);
1452                                 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1453                         }
1454                 }
1455                 else {
1456                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1457                         if (scoreboard_fadeoutspeed && frametime)
1458                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1459                         else
1460                                 scoreboard_fade_alpha = 0;
1461                 }
1462
1463                 if (!scoreboard_fade_alpha)
1464                         return;
1465         }
1466         else
1467                 scoreboard_fade_alpha = 0;
1468
1469         if (autocvar_hud_panel_scoreboard_dynamichud)
1470                 HUD_Scale_Enable();
1471         else
1472                 HUD_Scale_Disable();
1473
1474         if(scoreboard_fade_alpha <= 0)
1475                 return;
1476         panel_fade_alpha *= scoreboard_fade_alpha;
1477         HUD_Panel_LoadCvars();
1478
1479         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1480         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1481         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1482         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1483         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1484         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1485
1486         // don't overlap with con_notify
1487         if(!autocvar__hud_configure)
1488                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1489
1490         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1491         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1492         panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1493         panel_size.x = fixed_scoreboard_width;
1494
1495         Scoreboard_UpdatePlayerTeams();
1496
1497         vector pos = panel_pos;
1498         entity pl, tm;
1499         string str;
1500
1501         // Heading
1502         vector sb_heading_fontsize;
1503         sb_heading_fontsize = hud_fontsize * 2;
1504         draw_beginBoldFont();
1505         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1506         draw_endBoldFont();
1507
1508         pos.y += sb_heading_fontsize.y;
1509         if(panel.current_panel_bg != "0")
1510                 pos.y += panel_bg_border;
1511
1512         // Draw the scoreboard
1513         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1514         if(scale <= 0)
1515                 scale = 0.25;
1516         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1517
1518         if(teamplay)
1519         {
1520                 vector panel_bg_color_save = panel_bg_color;
1521                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1522                 if(panel.current_panel_bg != "0")
1523                         team_score_baseoffset.x -= panel_bg_border;
1524                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1525                 {
1526                         if(tm.team == NUM_SPECTATOR)
1527                                 continue;
1528                         if(!tm.team)
1529                                 continue;
1530
1531                         draw_beginBoldFont();
1532                         vector rgb = Team_ColorRGB(tm.team);
1533                         str = ftos(tm.(teamscores(ts_primary)));
1534                         drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1535
1536                         if(ts_primary != ts_secondary)
1537                         {
1538                                 str = ftos(tm.(teamscores(ts_secondary)));
1539                                 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);
1540                         }
1541                         draw_endBoldFont();
1542                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1543                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1544                         else if(panel_bg_color_team > 0)
1545                                 panel_bg_color = rgb * panel_bg_color_team;
1546                         else
1547                                 panel_bg_color = rgb;
1548                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1549                 }
1550                 panel_bg_color = panel_bg_color_save;
1551         }
1552         else
1553         {
1554                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1555                         if(tm.team != NUM_SPECTATOR)
1556                                 break;
1557                 // display it anyway
1558                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1559         }
1560
1561         if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1562                 if(race_speedaward) {
1563                         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);
1564                         pos.y += 1.25 * hud_fontsize.y;
1565                 }
1566                 if(race_speedaward_alltimebest) {
1567                         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);
1568                         pos.y += 1.25 * hud_fontsize.y;
1569                 }
1570                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1571         }
1572         else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1573                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1574
1575         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1576
1577         // List spectators
1578         for(pl = players.sort_next; pl; pl = pl.sort_next)
1579         {
1580                 if(pl.team == NUM_SPECTATOR)
1581                 {
1582                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1583                                 if(tm.team == NUM_SPECTATOR)
1584                                         break;
1585                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1586                         draw_beginBoldFont();
1587                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1588                         draw_endBoldFont();
1589                         pos.y += 1.25 * hud_fontsize.y;
1590
1591                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1592                         pos.y += 1.25 * hud_fontsize.y;
1593
1594                         break;
1595                 }
1596         }
1597
1598         // Print info string
1599         float tl, fl, ll;
1600         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1601         tl = STAT(TIMELIMIT);
1602         fl = STAT(FRAGLIMIT);
1603         ll = STAT(LEADLIMIT);
1604         if(gametype == MAPINFO_TYPE_LMS)
1605         {
1606                 if(tl > 0)
1607                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1608         }
1609         else
1610         {
1611                 if(tl > 0)
1612                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1613                 if(fl > 0)
1614                 {
1615                         if(tl > 0)
1616                                 str = strcat(str, _(" or"));
1617                         if(teamplay)
1618                         {
1619                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1620                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1621                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1622                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1623                         }
1624                         else
1625                         {
1626                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1627                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1628                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1629                                         TranslateScoresLabel(scores_label(ps_primary))));
1630                         }
1631                 }
1632                 if(ll > 0)
1633                 {
1634                         if(tl > 0 || fl > 0)
1635                                 str = strcat(str, _(" or"));
1636                         if(teamplay)
1637                         {
1638                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1639                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1640                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1641                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1642                         }
1643                         else
1644                         {
1645                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1646                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1647                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648                                         TranslateScoresLabel(scores_label(ps_primary))));
1649                         }
1650                 }
1651         }
1652
1653         pos.y += 1.2 * hud_fontsize.y;
1654         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1655
1656         // print information about respawn status
1657         float respawn_time = STAT(RESPAWN_TIME);
1658         if(!intermission)
1659         if(respawn_time)
1660         {
1661                 if(respawn_time < 0)
1662                 {
1663                         // a negative number means we are awaiting respawn, time value is still the same
1664                         respawn_time *= -1; // remove mark now that we checked it
1665
1666                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1667                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1668                         else
1669                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1670                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1671                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1672                                                 :
1673                                                 count_seconds(ceil(respawn_time - time))
1674                                         )
1675                                 );
1676                 }
1677                 else if(time < respawn_time)
1678                 {
1679                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1680                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1681                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1682                                         :
1683                                         count_seconds(ceil(respawn_time - time))
1684                                 )
1685                         );
1686                 }
1687                 else if(time >= respawn_time)
1688                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1689
1690                 pos.y += 1.2 * hud_fontsize.y;
1691                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1692         }
1693
1694         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1695 }