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