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