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