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