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