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