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