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