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