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