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