]> de.git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
54bb4671d3a19b55371b9dc68562aec50c1fda43
[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 && !ISGAMETYPE(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, ((ISGAMETYPE(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         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1463         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1464         {
1465                 float t;
1466                 t = grecordtime[i];
1467                 if (t == 0)
1468                         continue;
1469
1470                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1471                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1472                 else if(!((j + column) & 1) && sbt_highlight)
1473                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1474
1475                 str = count_ordinal(i+1);
1476                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1477                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1478                 str = ColorTranslateRGB(grecordholder[i]);
1479                 if(cut)
1480                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1481                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1482
1483                 pos.y += 1.25 * hud_fontsize.y;
1484                 j++;
1485                 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1486                 {
1487                         column++;
1488                         j = 0;
1489                         pos.x += panel_size.x / columns;
1490                         pos.y = panel_pos.y;
1491                 }
1492         }
1493         strfree(zoned_name_self);
1494
1495         panel_size.x += panel_bg_padding * 2; // restore initial width
1496         return end_pos;
1497 }
1498
1499 float scoreboard_time;
1500 bool have_weapon_stats;
1501 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1502 {
1503         if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1504                 return false;
1505         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1506                 return false;
1507
1508         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1509                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1510                 && !intermission)
1511         {
1512                 return false;
1513         }
1514
1515         if (!have_weapon_stats)
1516         {
1517                 FOREACH(Weapons, it != WEP_Null, {
1518                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1519                         if (weapon_stats >= 0)
1520                         {
1521                                 have_weapon_stats = true;
1522                                 break;
1523                         }
1524                 });
1525                 if (!have_weapon_stats)
1526                         return false;
1527         }
1528
1529         return true;
1530 }
1531
1532 void Scoreboard_Draw()
1533 {
1534         if(!autocvar__hud_configure)
1535         {
1536                 if(!hud_draw_maximized) return;
1537
1538                 // frametime checks allow to toggle the scoreboard even when the game is paused
1539                 if(scoreboard_active) {
1540                         if (scoreboard_fade_alpha < 1)
1541                                 scoreboard_time = time;
1542                         if(hud_configure_menu_open == 1)
1543                                 scoreboard_fade_alpha = 1;
1544                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1545                         if (scoreboard_fadeinspeed && frametime)
1546                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1547                         else
1548                                 scoreboard_fade_alpha = 1;
1549                         if(hud_fontsize_str != autocvar_hud_fontsize)
1550                         {
1551                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1552                                 Scoreboard_initFieldSizes();
1553                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1554                         }
1555                 }
1556                 else {
1557                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1558                         if (scoreboard_fadeoutspeed && frametime)
1559                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1560                         else
1561                                 scoreboard_fade_alpha = 0;
1562                 }
1563
1564                 if (!scoreboard_fade_alpha)
1565                 {
1566                         scoreboard_acc_fade_alpha = 0;
1567                         return;
1568                 }
1569         }
1570         else
1571                 scoreboard_fade_alpha = 0;
1572
1573         if (autocvar_hud_panel_scoreboard_dynamichud)
1574                 HUD_Scale_Enable();
1575         else
1576                 HUD_Scale_Disable();
1577
1578         if(scoreboard_fade_alpha <= 0)
1579                 return;
1580         panel_fade_alpha *= scoreboard_fade_alpha;
1581         HUD_Panel_LoadCvars();
1582
1583         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1584         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1585         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1586         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1587         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1588         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1589
1590         // don't overlap with con_notify
1591         if(!autocvar__hud_configure)
1592                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1593
1594         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1595         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1596         panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1597         panel_size.x = fixed_scoreboard_width;
1598
1599         Scoreboard_UpdatePlayerTeams();
1600
1601         vector pos = panel_pos;
1602         entity pl, tm;
1603         string str;
1604         vector str_pos;
1605
1606         // Heading
1607         vector sb_heading_fontsize;
1608         sb_heading_fontsize = hud_fontsize * 2;
1609         draw_beginBoldFont();
1610         drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1611         draw_endBoldFont();
1612
1613         pos.y += sb_heading_fontsize.y;
1614         if(panel.current_panel_bg != "0")
1615                 pos.y += panel_bg_border;
1616
1617         // Draw the scoreboard
1618         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1619         if(scale <= 0)
1620                 scale = 0.25;
1621         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1622
1623         if(teamplay)
1624         {
1625                 vector panel_bg_color_save = panel_bg_color;
1626                 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1627                 if(panel.current_panel_bg != "0")
1628                         team_score_baseoffset.x -= panel_bg_border;
1629                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1630                 {
1631                         if(tm.team == NUM_SPECTATOR)
1632                                 continue;
1633                         if(!tm.team)
1634                                 continue;
1635
1636                         draw_beginBoldFont();
1637                         vector rgb = Team_ColorRGB(tm.team);
1638                         str = ftos(tm.(teamscores(ts_primary)));
1639                         str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1640                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1641
1642                         if(ts_primary != ts_secondary)
1643                         {
1644                                 str = ftos(tm.(teamscores(ts_secondary)));
1645                                 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1646                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1647                         }
1648                         draw_endBoldFont();
1649                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1650                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1651                         else if(panel_bg_color_team > 0)
1652                                 panel_bg_color = rgb * panel_bg_color_team;
1653                         else
1654                                 panel_bg_color = rgb;
1655                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1656                 }
1657                 panel_bg_color = panel_bg_color_save;
1658         }
1659         else
1660         {
1661                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1662                         if(tm.team != NUM_SPECTATOR)
1663                                 break;
1664                 // display it anyway
1665                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1666         }
1667
1668         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1669                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1670
1671         if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1672                 if(race_speedaward) {
1673                         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);
1674                         pos.y += 1.25 * hud_fontsize.y;
1675                 }
1676                 if(race_speedaward_alltimebest) {
1677                         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);
1678                         pos.y += 1.25 * hud_fontsize.y;
1679                 }
1680                 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1681         }
1682
1683         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1684
1685         // List spectators
1686         for(pl = players.sort_next; pl; pl = pl.sort_next)
1687         {
1688                 if(pl.team == NUM_SPECTATOR)
1689                 {
1690                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
1691                                 if(tm.team == NUM_SPECTATOR)
1692                                         break;
1693                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1694                         draw_beginBoldFont();
1695                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1696                         draw_endBoldFont();
1697                         pos.y += 1.25 * hud_fontsize.y;
1698
1699                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1700                         pos.y += 1.25 * hud_fontsize.y;
1701
1702                         break;
1703                 }
1704         }
1705
1706         // Print info string
1707         float tl, fl, ll;
1708         str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1709         tl = STAT(TIMELIMIT);
1710         fl = STAT(FRAGLIMIT);
1711         ll = STAT(LEADLIMIT);
1712         if(ISGAMETYPE(LMS))
1713         {
1714                 if(tl > 0)
1715                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1716         }
1717         else
1718         {
1719                 if(tl > 0)
1720                         str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1721                 if(fl > 0)
1722                 {
1723                         if(tl > 0)
1724                                 str = strcat(str, _(" or"));
1725                         if(teamplay)
1726                         {
1727                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1728                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1729                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1730                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1731                         }
1732                         else
1733                         {
1734                                 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1735                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1736                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1737                                         TranslateScoresLabel(scores_label(ps_primary))));
1738                         }
1739                 }
1740                 if(ll > 0)
1741                 {
1742                         if(tl > 0 || fl > 0)
1743                                 str = strcat(str, _(" or"));
1744                         if(teamplay)
1745                         {
1746                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1747                                         (teamscores_label(ts_primary) == "score")   ? CTX(_("SCO^points")) :
1748                                         (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1749                                         TranslateScoresLabel(teamscores_label(ts_primary))));
1750                         }
1751                         else
1752                         {
1753                                 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1754                                         (scores_label(ps_primary) == "score")   ? CTX(_("SCO^points")) :
1755                                         (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1756                                         TranslateScoresLabel(scores_label(ps_primary))));
1757                         }
1758                 }
1759         }
1760
1761         pos.y += 1.2 * hud_fontsize.y;
1762         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1763
1764         // print information about respawn status
1765         float respawn_time = STAT(RESPAWN_TIME);
1766         if(!intermission)
1767         if(respawn_time)
1768         {
1769                 if(respawn_time < 0)
1770                 {
1771                         // a negative number means we are awaiting respawn, time value is still the same
1772                         respawn_time *= -1; // remove mark now that we checked it
1773
1774                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
1775                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1776                         else
1777                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
1778                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1779                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1780                                                 :
1781                                                 count_seconds(ceil(respawn_time - time))
1782                                         )
1783                                 );
1784                 }
1785                 else if(time < respawn_time)
1786                 {
1787                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1788                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1789                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1790                                         :
1791                                         count_seconds(ceil(respawn_time - time))
1792                                 )
1793                         );
1794                 }
1795                 else if(time >= respawn_time)
1796                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1797
1798                 pos.y += 1.2 * hud_fontsize.y;
1799                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1800         }
1801
1802         scoreboard_bottom = pos.y + 2 * hud_fontsize.y;
1803 }