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