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