]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Merge branch 'terencehill/match_end_restore_status' into 'master'
[xonotic/xonotic-data.pk3dir.git] / qcsrc / client / hud / panel / scoreboard.qc
1 #include "scoreboard.qh"
2
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/physics.qh>
6 #include <client/hud/panel/quickmenu.qh>
7 #include <client/hud/panel/racetimer.qh>
8 #include <client/hud/panel/weapons.qh>
9 #include <common/constants.qh>
10 #include <common/ent_cs.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/net_linked.qh>
14 #include <common/scores.qh>
15 #include <common/stats.qh>
16 #include <common/teams.qh>
17 #include <common/items/inventory.qh>
18
19 // Scoreboard (#24)
20
21 void Scoreboard_Draw_Export(int fh)
22 {
23         // allow saving cvars that aesthetically change the panel into hud skin files
24         HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
25         HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
26         HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
27         HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
28         HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
29         HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
30         HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
31         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
32         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
33         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
34         HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
35         HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
36         HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
37         HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38         HUD_Write_Cvar("hud_panel_scoreboard_spectators_position");
39 }
40
41 const int MAX_SBT_FIELDS = MAX_SCORE;
42
43 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
44 float sbt_field_size[MAX_SBT_FIELDS + 1];
45 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 float sbt_field_title_condense_factor[MAX_SBT_FIELDS + 1];
47 float sbt_field_title_width[MAX_SBT_FIELDS + 1];
48 int sbt_num_fields;
49 float sbt_field_title_maxwidth;
50 float sbt_field_title_maxwidth_factor;
51
52 string autocvar_hud_fontsize;
53 float max_namesize;
54 float name_field_index;
55 int sb_field_sizes_init;
56
57 float sbt_bg_alpha;
58 float sbt_fg_alpha;
59 float sbt_fg_alpha_self;
60 bool sbt_highlight;
61 float sbt_highlight_alpha;
62 float sbt_highlight_alpha_self;
63 float sbt_highlight_alpha_eliminated;
64
65 // provide basic panel cvars to old clients
66 // TODO remove them after a future release (0.8.2+)
67 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
68 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
69 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
70 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
71 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
72 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
73 noref string autocvar_hud_panel_scoreboard_bg_border = "";
74 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
75
76 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
77 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
78 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
79 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
80 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
81 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
82 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
83 float autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth = 0.07;
84 bool autocvar_hud_panel_scoreboard_table_highlight = true;
85 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
86 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
87 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
88 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
89 float autocvar_hud_panel_scoreboard_team_size_position = 0;
90 float autocvar_hud_panel_scoreboard_spectators_position = 1;
91
92 bool autocvar_hud_panel_scoreboard_accuracy = true;
93 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
94 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
95 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
96 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
97
98 bool autocvar_hud_panel_scoreboard_itemstats = true;
99 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
100 int autocvar_hud_panel_scoreboard_itemstats_filter = 1;
101 int autocvar_hud_panel_scoreboard_itemstats_filter_mask = 12;
102 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
103 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
104
105 bool autocvar_hud_panel_scoreboard_dynamichud = false;
106
107 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
108 bool autocvar_hud_panel_scoreboard_others_showscore = true;
109 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
110 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
111 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
112 bool autocvar_hud_panel_scoreboard_playerid = false;
113 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
114 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
115 bool autocvar_hud_panel_scoreboard_scores_per_round;
116
117 float scoreboard_time;
118
119 SHUTDOWN(scoreboard)
120 {
121         if(autocvar_hud_panel_scoreboard_scores_per_round)
122                 cvar_set("hud_panel_scoreboard_scores_per_round", "0");
123 }
124
125 // mode 0: returns translated label
126 // mode 1: prints name and description of all the labels
127 string Label_getInfo(string label, int mode)
128 {
129         if (mode == 1)
130                 label = "bckills"; // first case in the switch
131
132 #define SCO_LABEL(strlabel, label, padding, help) \
133         case label: \
134                 if (!mode) \
135                         return CTX(strlabel); \
136                 LOG_HELP("^3", label, padding, "^7", help);
137
138         switch(label)
139         {
140                 SCO_LABEL(_("SCO^bckills"),       "bckills", "            ", _("Number of ball carrier kills"));
141                 SCO_LABEL(_("SCO^bctime"),        "bctime", "             ", _("Total amount of time holding the ball in Keepaway"));
142                 SCO_LABEL(_("SCO^caps"),          "caps", "               ", _("How often a flag (CTF) or a key (KeyHunt) was captured"));
143                 SCO_LABEL(_("SCO^captime"),       "captime", "            ", _("Time of fastest capture (CTF)"));
144                 SCO_LABEL(_("SCO^deaths"),        "deaths", "             ", _("Number of deaths"));
145                 SCO_LABEL(_("SCO^destructions"),  "destructions", "       ", _("Number of keys destroyed by pushing them into void"));
146                 SCO_LABEL(_("SCO^damage dealt"),  "dmg", "                ", _("The total damage dealt"));
147                 SCO_LABEL(_("SCO^damage taken"),  "dmgtaken", "           ", _("The total damage taken"));
148                 SCO_LABEL(_("SCO^drops"),         "drops", "              ", _("Number of flag drops"));
149                 SCO_LABEL(_("SCO^elo"),           "elo", "                ", _("Player ELO"));
150                 SCO_LABEL(_("SCO^fastest"),       "fastest", "            ", _("Time of fastest lap (Race/CTS)"));
151                 SCO_LABEL(_("SCO^faults"),        "faults", "             ", _("Number of faults committed"));
152                 SCO_LABEL(_("SCO^fckills"),       "fckills", "            ", _("Number of flag carrier kills"));
153                 SCO_LABEL(_("SCO^fps"),           "fps", "                ", _("FPS"));
154                 SCO_LABEL(_("SCO^frags"),         "frags", "              ", _("Number of kills minus suicides"));
155                 SCO_LABEL(_("SCO^generators"),    "generators", "         ", _("Number of generators destroyed"));
156                 SCO_LABEL(_("SCO^goals"),         "goals", "              ", _("Number of goals scored"));
157                 SCO_LABEL(_("SCO^hunts"),         "hunts", "              ", _("Number of hunts (Survival)"));
158                 SCO_LABEL(_("SCO^kckills"),       "kckills", "            ", _("Number of keys carrier kills"));
159                 SCO_LABEL(_("SCO^k/d"),           "kd", "                 ", _("The kill-death ratio"));
160                 SCO_LABEL(_("SCO^kdr"),           "kdr", "                ", _("The kill-death ratio"));
161                 SCO_LABEL(_("SCO^kdratio"),       "kdratio", "            ", _("The kill-death ratio"));
162                 SCO_LABEL(_("SCO^kills"),         "kills", "              ", _("Number of kills"));
163                 SCO_LABEL(_("SCO^laps"),          "laps", "               ", _("Number of laps finished (Race/CTS)"));
164                 SCO_LABEL(_("SCO^lives"),         "lives", "              ", _("Number of lives (LMS)"));
165                 SCO_LABEL(_("SCO^losses"),        "losses", "             ", _("Number of times a key was lost"));
166                 SCO_LABEL(_("SCO^name"),          "name", "               ", _("Player name"));
167                 SCO_LABEL(_("SCO^nick"),          "nick", "               ", _("Player name"));
168                 SCO_LABEL(_("SCO^objectives"),    "objectives", "         ", _("Number of objectives destroyed"));
169                 SCO_LABEL(_("SCO^pickups"),       "pickups", "            ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
170                 SCO_LABEL(_("SCO^ping"),          "ping", "               ", _("Ping time"));
171                 SCO_LABEL(_("SCO^pl"),            "pl", "                 ", _("Packet loss"));
172                 SCO_LABEL(_("SCO^pushes"),        "pushes", "             ", _("Number of players pushed into void"));
173                 SCO_LABEL(_("SCO^rank"),          "rank", "               ", _("Player rank"));
174                 SCO_LABEL(_("SCO^returns"),       "returns", "            ", _("Number of flag returns"));
175                 SCO_LABEL(_("SCO^revivals"),      "revivals", "           ", _("Number of revivals"));
176                 SCO_LABEL(_("SCO^rounds won"),    "rounds", "             ", _("Number of rounds won"));
177                 SCO_LABEL(_("SCO^rounds played"), "rounds_pl", "          ", _("Number of rounds played"));
178                 SCO_LABEL(_("SCO^score"),         "score", "              ", _("Total score"));
179                 SCO_LABEL(_("SCO^suicides"),      "suicides", "           ", _("Number of suicides"));
180                 SCO_LABEL(_("SCO^sum"),           "sum", "                ", _("Number of kills minus deaths"));
181                 SCO_LABEL(_("SCO^survivals"),     "survivals", "          ", _("Number of survivals"));
182                 SCO_LABEL(_("SCO^takes"),         "takes", "              ", _("Number of domination points taken (Domination)"));
183                 SCO_LABEL(_("SCO^teamkills"),     "teamkills", "          ", _("Number of teamkills"));
184                 SCO_LABEL(_("SCO^ticks"),         "ticks", "              ", _("Number of ticks (Domination)"));
185                 SCO_LABEL(_("SCO^time"),          "time", "               ", _("Total time raced (Race/CTS)"));
186         }
187         return label;
188 #undef SCO_LABEL
189 }
190
191 bool scoreboard_ui_disabling;
192 void HUD_Scoreboard_UI_Disable()
193 {
194         scoreboard_ui_disabling = true;
195         sb_showscores = false;
196 }
197
198 void HUD_Scoreboard_UI_Disable_Instantly()
199 {
200         scoreboard_ui_disabling = false;
201         scoreboard_ui_enabled = 0;
202         scoreboard_selected_panel = 0;
203         scoreboard_selected_player = NULL;
204         scoreboard_selected_team = NULL;
205 }
206
207 // mode: 0 normal, 1 team selection
208 void Scoreboard_UI_Enable(int mode)
209 {
210         if(isdemo()) return;
211
212         if (mode == 1)
213         {
214                 if (scoreboard_ui_enabled == 2 || !teamplay || intermission)
215                         return;
216
217                 // release player's pressed keys as they aren't released elsewhere
218                 // in particular jump needs to be released as it may open the team selection
219                 // (when server detects jump has been pressed it sends the command to open the team selection)
220                 Release_Common_Keys();
221                 scoreboard_ui_enabled = 2;
222                 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
223         }
224         else
225         {
226                 if (scoreboard_ui_enabled == 1)
227                         return;
228                 scoreboard_ui_enabled = 1;
229                 scoreboard_selected_panel = SB_PANEL_FIRST;
230         }
231         scoreboard_selected_player = NULL;
232         scoreboard_selected_team = NULL;
233         scoreboard_selected_panel_time = time;
234 }
235
236 int rankings_start_column;
237 int rankings_rows = 0;
238 int rankings_columns = 0;
239 int rankings_cnt = 0;
240 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
241 {
242         string s;
243
244         if(!scoreboard_ui_enabled || scoreboard_ui_disabling)
245                 return false;
246
247         if(bInputType == 3)
248         {
249                 mousepos.x = nPrimary;
250                 mousepos.y = nSecondary;
251                 return true;
252         }
253
254         if(bInputType == 2)
255                 return false;
256
257         // at this point bInputType can only be 0 or 1 (key pressed or released)
258         bool key_pressed = (bInputType == 0);
259
260         // ESC to exit (TAB-ESC works too)
261         if(nPrimary == K_ESCAPE)
262         {
263                 if (!key_pressed)
264                         return true;
265                 HUD_Scoreboard_UI_Disable();
266                 return true;
267         }
268
269         // block any input while a menu dialog is fading
270         if(autocvar__menu_alpha)
271         {
272                 hudShiftState = 0;
273                 return true;
274         }
275
276         // allow console bind to work
277         string con_keys = findkeysforcommand("toggleconsole", 0);
278         int keys = tokenize(con_keys); // findkeysforcommand returns data for this
279
280         bool hit_con_bind = false;
281         int i;
282         for (i = 0; i < keys; ++i)
283         {
284                 if(nPrimary == stof(argv(i)))
285                         hit_con_bind = true;
286         }
287
288         if(key_pressed) {
289                 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
290                 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
291                 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
292                 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
293         }
294         else {
295                 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
296                 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
297                 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
298                 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
299         }
300
301         if(nPrimary == K_TAB)
302         {
303                 if (!key_pressed)
304                         return true;
305                 if (scoreboard_ui_enabled == 2)
306                 {
307                         if (hudShiftState & S_SHIFT)
308                                 goto uparrow_action;
309                         else
310                                 goto downarrow_action;
311                 }
312
313                 if (hudShiftState & S_SHIFT)
314                 {
315                         --scoreboard_selected_panel;
316                         if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
317                                 --scoreboard_selected_panel;
318                         if (scoreboard_selected_panel < SB_PANEL_FIRST)
319                                 scoreboard_selected_panel = SB_PANEL_MAX;
320                 }
321                 else
322                 {
323                         ++scoreboard_selected_panel;
324                         if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
325                                 ++scoreboard_selected_panel;
326                         if (scoreboard_selected_panel > SB_PANEL_MAX)
327                                 scoreboard_selected_panel = SB_PANEL_FIRST;
328                 }
329
330                 scoreboard_selected_panel_time = time;
331         }
332         else if(nPrimary == K_DOWNARROW)
333         {
334                 if (!key_pressed)
335                         return true;
336                 LABEL(downarrow_action);
337                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
338                 {
339                         if (scoreboard_ui_enabled == 2)
340                         {
341                                 entity curr_team = NULL;
342                                 bool scoreboard_selected_team_found = false;
343                                 if (!scoreboard_selected_team)
344                                         scoreboard_selected_team_found = true;
345
346                                 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
347                                 {
348                                         if(tm.team == NUM_SPECTATOR)
349                                                 continue;
350                                         curr_team = tm;
351                                         if (scoreboard_selected_team_found)
352                                                 goto ok_team;
353                                         if (scoreboard_selected_team == tm)
354                                                 scoreboard_selected_team_found = true;
355                                 }
356                                 LABEL(ok_team);
357                                 if (curr_team == scoreboard_selected_team) // loop reached the last team
358                                         curr_team = NULL;
359                                 scoreboard_selected_team = curr_team;
360                         }
361                         else
362                         {
363                                 entity pl, tm;
364                                 entity curr_pl = NULL;
365                                 bool scoreboard_selected_player_found = false;
366                                 if (!scoreboard_selected_player)
367                                         scoreboard_selected_player_found = true;
368
369                                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
370                                 {
371                                         if(tm.team != NUM_SPECTATOR)
372                                         for(pl = players.sort_next; pl; pl = pl.sort_next)
373                                         {
374                                                 if(pl.team != tm.team)
375                                                         continue;
376                                                 curr_pl = pl;
377                                                 if (scoreboard_selected_player_found)
378                                                         goto ok_done;
379                                                 if (scoreboard_selected_player == pl)
380                                                         scoreboard_selected_player_found = true;
381                                         }
382                                 }
383                                 LABEL(ok_done);
384                                 if (curr_pl == scoreboard_selected_player) // loop reached the last player
385                                         curr_pl = NULL;
386                                 scoreboard_selected_player = curr_pl;
387                         }
388                 }
389         }
390         else if(nPrimary == K_UPARROW)
391         {
392                 if (!key_pressed)
393                         return true;
394                 LABEL(uparrow_action);
395                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
396                 {
397                         if (scoreboard_ui_enabled == 2)
398                         {
399                                 entity prev_team = NULL;
400                                 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
401                                 {
402                                         if(tm.team == NUM_SPECTATOR)
403                                                 continue;
404                                         if (tm == scoreboard_selected_team)
405                                                 goto ok_team2;
406                                         prev_team = tm;
407                                 }
408                                 LABEL(ok_team2);
409                                 scoreboard_selected_team = prev_team;
410                         }
411                         else
412                         {
413                                 entity prev_pl = NULL;
414                                 entity pl, tm;
415                                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
416                                 {
417                                         if(tm.team != NUM_SPECTATOR)
418                                         for(pl = players.sort_next; pl; pl = pl.sort_next)
419                                         {
420                                                 if(pl.team != tm.team)
421                                                         continue;
422                                                 if (pl == scoreboard_selected_player)
423                                                         goto ok_done2;
424                                                 prev_pl = pl;
425                                         }
426                                 }
427                                 LABEL(ok_done2);
428                                 scoreboard_selected_player = prev_pl;
429                         }
430                 }
431         }
432         else if(nPrimary == K_RIGHTARROW)
433         {
434                 if (!key_pressed)
435                         return true;
436                 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
437                         rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
438         }
439         else if(nPrimary == K_LEFTARROW)
440         {
441                 if (!key_pressed)
442                         return true;
443                 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
444                         rankings_start_column = max(rankings_start_column - 1, 0);
445         }
446         else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
447         {
448                 if (!key_pressed)
449                         return true;
450                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
451                 {
452                         if (scoreboard_ui_enabled == 2)
453                         {
454                                 string team_name;
455                                 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
456                                         team_name = "auto";
457                                 else
458                                         team_name = Static_Team_ColorName(scoreboard_selected_team.team);
459                                 localcmd(sprintf("cmd selectteam %s; cmd join\n", team_name));
460                                 HUD_Scoreboard_UI_Disable();
461                         }
462                         else if (scoreboard_selected_player)
463                                 localcmd(sprintf("spectate %d\n", scoreboard_selected_player.sv_entnum + 1));
464                 }
465         }
466         else if(nPrimary == 'c' && (hudShiftState & S_CTRL))
467         {
468                 if (!key_pressed)
469                         return true;
470                 if (scoreboard_ui_enabled == 1 && scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
471                 {
472                         switch (scoreboard_selected_columns_layout)
473                         {
474                                 case 0:
475                                         if (autocvar_scoreboard_columns != "" && autocvar_scoreboard_columns != "all" && autocvar_scoreboard_columns != "default")
476                                         {
477                                                 localcmd(sprintf("scoreboard_columns_set\n")); // sets the layout saved in scoreboard_columns
478                                                 scoreboard_selected_columns_layout = 1;
479                                                 break;
480                                         }
481                                         // fallthrough
482                                 case 1:
483                                         localcmd(sprintf("scoreboard_columns_set default\n"));
484                                         scoreboard_selected_columns_layout = 2;
485                                         break;
486                                 case 2:
487                                         localcmd(sprintf("scoreboard_columns_set all\n"));
488                                         scoreboard_selected_columns_layout = 0;
489                                         break;
490                         }
491                 }
492         }
493         else if(nPrimary == 'r' && (hudShiftState & S_CTRL))
494         {
495                 if (!key_pressed)
496                         return true;
497                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
498                         localcmd("toggle hud_panel_scoreboard_scores_per_round\n");
499         }
500         else if(nPrimary == 't' && (hudShiftState & S_CTRL))
501         {
502                 if (!key_pressed)
503                         return true;
504                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
505                 {
506                         if (scoreboard_selected_player)
507                         {
508                                 localcmd(sprintf("commandmode tell \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
509                                 HUD_Scoreboard_UI_Disable();
510                         }
511                 }
512         }
513         else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
514         {
515                 if (!key_pressed)
516                         return true;
517                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
518                 {
519                         if (scoreboard_selected_player)
520                                 localcmd(sprintf("vcall kick \"%s^7\"\n", entcs_GetName(scoreboard_selected_player.sv_entnum)));
521                 }
522         }
523         else if(hit_con_bind || nPrimary == K_PAUSE)
524                 return false;
525
526         return true;
527 }
528
529 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
530 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
531
532 void Scoreboard_InitScores()
533 {
534         int i, f;
535
536         ps_primary = ps_secondary = NULL;
537         ts_primary = ts_secondary = -1;
538         FOREACH(Scores, true, {
539                 if(scores_flags(it) & SFL_NOT_SORTABLE)
540                         continue;
541                 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
542                 if(f == SFL_SORT_PRIO_PRIMARY)
543                         ps_primary = it;
544                 if(f == SFL_SORT_PRIO_SECONDARY)
545                         ps_secondary = it;
546         });
547         if(ps_secondary == NULL)
548                 ps_secondary = ps_primary;
549
550         for(i = 0; i < MAX_TEAMSCORE; ++i)
551         {
552                 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
553                 if(f == SFL_SORT_PRIO_PRIMARY)
554                         ts_primary = i;
555                 if(f == SFL_SORT_PRIO_SECONDARY)
556                         ts_secondary = i;
557         }
558         if(ts_secondary == -1)
559                 ts_secondary = ts_primary;
560
561         Cmd_Scoreboard_SetFields(0);
562 }
563
564 //float lastpnum;
565 void Scoreboard_UpdatePlayerTeams()
566 {
567         static float update_time;
568         if (time <= update_time)
569                 return;
570         update_time = time;
571
572         entity pl, tmp;
573         numplayers = 0;
574         //int num = 0;
575         for(pl = players.sort_next; pl; pl = pl.sort_next)
576         {
577                 numplayers += pl.team != NUM_SPECTATOR;
578                 //num += 1;
579                 int Team = entcs_GetScoreTeam(pl.sv_entnum);
580                 if(SetTeam(pl, Team))
581                 {
582                         tmp = pl.sort_prev;
583                         Scoreboard_UpdatePlayerPos(pl);
584                         if(tmp)
585                                 pl = tmp;
586                         else
587                                 pl = players.sort_next;
588                 }
589         }
590         /*
591         if(num != lastpnum)
592                 print(strcat("PNUM: ", ftos(num), "\n"));
593         lastpnum = num;
594         */
595 }
596
597 int Scoreboard_CompareScore(int vl, int vr, int f)
598 {
599         TC(int, vl); TC(int, vr); TC(int, f);
600         if(f & SFL_ZERO_IS_WORST)
601         {
602                 if(vl == 0 && vr != 0)
603                         return 1;
604                 if(vl != 0 && vr == 0)
605                         return 0;
606         }
607         if(vl > vr)
608                 return IS_INCREASING(f);
609         if(vl < vr)
610                 return IS_DECREASING(f);
611         return -1;
612 }
613
614 float Scoreboard_ComparePlayerScores(entity left, entity right)
615 {
616         int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
617         int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
618
619         if(vl > vr)
620                 return true;
621         if(vl < vr)
622                 return false;
623
624         if(vl == NUM_SPECTATOR)
625         {
626                 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
627                 // no other sorting
628                 if(!left.gotscores && right.gotscores)
629                         return true;
630                 return false;
631         }
632
633         int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
634         if (res >= 0) return res;
635
636         if (ps_secondary && ps_secondary != ps_primary)
637         {
638                 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
639                 if (res >= 0) return res;
640         }
641
642         FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
643                 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
644                 if (res >= 0) return res;
645         });
646
647         if (left.sv_entnum < right.sv_entnum)
648                 return true;
649
650         return false;
651 }
652
653 void Scoreboard_UpdatePlayerPos(entity player)
654 {
655         entity ent;
656         for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
657         {
658                 SORT_SWAP(player, ent);
659         }
660         for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
661         {
662                 SORT_SWAP(ent, player);
663         }
664 }
665
666 float Scoreboard_CompareTeamScores(entity left, entity right)
667 {
668         if(left.team == NUM_SPECTATOR)
669                 return 1;
670         if(right.team == NUM_SPECTATOR)
671                 return 0;
672
673         int fld_idx = -1;
674         int r;
675         for(int i = -2; i < MAX_TEAMSCORE; ++i)
676         {
677                 if (i < 0)
678                 {
679                         if (fld_idx == -1) fld_idx = ts_primary;
680                         else if (ts_secondary == ts_primary) continue;
681                         else fld_idx = ts_secondary;
682                 }
683                 else
684                 {
685                         fld_idx = i;
686                         if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
687                 }
688
689                 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
690                 if (r >= 0) return r;
691         }
692
693         if (left.team < right.team)
694                 return true;
695
696         return false;
697 }
698
699 void Scoreboard_UpdateTeamPos(entity Team)
700 {
701         entity ent;
702         for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
703         {
704                 SORT_SWAP(Team, ent);
705         }
706         for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
707         {
708                 SORT_SWAP(ent, Team);
709         }
710 }
711
712 void Cmd_Scoreboard_Help()
713 {
714         LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
715         LOG_HELP(_("Usage:"));
716         LOG_HELP("^2scoreboard_columns_set ^3default");
717         LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
718         LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
719         LOG_HELP(_("  ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
720         LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
721         LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
722         LOG_HELP(_("The following field names are recognized (case insensitive):"));
723         LOG_HELP("");
724
725         PrintScoresLabels();
726         LOG_HELP("");
727
728         LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
729                 "of game types, then a slash, to make the field show up only in these\n"
730                 "or in all but these game types. You can also specify 'all' as a\n"
731                 "field to show all fields available for the current game mode."));
732         LOG_HELP("");
733
734         LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
735                 "include/exclude ALL teams/noteams game modes."));
736         LOG_HELP("");
737
738         LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
739         LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
740                 "right of the vertical bar aligned to the right."));
741         LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
742                         "other gamemodes except DM."));
743 }
744
745 // NOTE: adding a gametype with ? to not warn for an optional field
746 // make sure it's excluded in a previous exclusive rule, if any
747 // otherwise the previous exclusive rule warns anyway
748 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
749 #define SCOREBOARD_DEFAULT_COLUMNS \
750 "ping pl fps name |" \
751 " -teams,rc,cts,surv,inv,lms/kills +ft,tdm,tmayhem/kills ?+rc,inv/kills" \
752 " -teams,surv,lms/deaths +ft,tdm,tmayhem/deaths" \
753 " +tdm/sum" \
754 " -teams,lms,rc,cts,surv,inv,ka/suicides +ft,tdm,tmayhem/suicides ?+rc,inv/suicides" \
755 " -cts,dm,tdm,surv,ka,ft,mayhem,tmayhem/frags" /* tdm already has this in "score" */ \
756 " +tdm,ft,dom,ons,as,tmayhem/teamkills"\
757 " -rc,cts,surv,nb/dmg -rc,cts,surv,nb/dmgtaken" \
758 " +surv/survivals +surv/hunts" \
759 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
760 " +lms/lives +lms/rank" \
761 " +kh/kckills +kh/losses +kh/caps" \
762 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
763 " +as/objectives +nb/faults +nb/goals" \
764 " +ka,tka/pickups +ka,tka/bckills +ka,tka/bctime +ft/revivals" \
765 " +dom/ticks +dom/takes" \
766 " -lms,rc,cts,inv,nb/score"
767
768 void Cmd_Scoreboard_SetFields(int argc)
769 {
770         TC(int, argc);
771         int i, slash;
772         string str, pattern;
773         bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
774         int missing;
775
776         if(!gametype)
777                 return; // do nothing, we don't know gametype and scores yet
778
779         // sbt_fields uses strunzone on the titles!
780         if(!sbt_field_title[0])
781                 for(i = 0; i < MAX_SBT_FIELDS; ++i)
782                         sbt_field_title[i] = strzone("(null)");
783
784         // TODO: re enable with gametype dependant cvars?
785         if(argc < 3) // no arguments provided
786                 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
787
788         if(argc < 3)
789                 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
790
791         if(argc == 3)
792         {
793                 if(argv(2) == "default" || argv(2) == "expand_default")
794                 {
795                         if(argv(2) == "expand_default")
796                                 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
797                         argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
798                 }
799                 else if(argv(2) == "all" || argv(2) == "ALL")
800                 {
801                         string s = "ping pl name |"; // scores without label (not really scores)
802                         if(argv(2) == "ALL")
803                         {
804                                 // scores without label
805                                 s = strcat(s, " ", "sum");
806                                 s = strcat(s, " ", "kdratio");
807                                 s = strcat(s, " ", "frags");
808                         }
809                         FOREACH(Scores, true, {
810                                 if(it != ps_primary)
811                                 if(it != ps_secondary)
812                                 if(scores_label(it) != "")
813                                         s = strcat(s, " ", scores_label(it));
814                         });
815                         if(ps_secondary != ps_primary)
816                                 s = strcat(s, " ", scores_label(ps_secondary));
817                         s = strcat(s, " ", scores_label(ps_primary));
818                         argc = tokenizebyseparator(strcat("0 1 ", s), " ");
819                 }
820         }
821
822
823         sbt_num_fields = 0;
824
825         hud_fontsize = HUD_GetFontsize("hud_fontsize");
826
827         for(i = 1; i < argc - 1; ++i)
828         {
829                 str = argv(i+1);
830                 bool nocomplain = false;
831                 if(substring(str, 0, 1) == "?")
832                 {
833                         nocomplain = true;
834                         str = substring(str, 1, strlen(str) - 1);
835                 }
836
837                 slash = strstrofs(str, "/", 0);
838                 if(slash >= 0)
839                 {
840                         pattern = substring(str, 0, slash);
841                         str = substring(str, slash + 1, strlen(str) - (slash + 1));
842
843                         if (!isGametypeInFilter(gametype, teamplay, false, pattern))
844                                 continue;
845                 }
846
847                 str = strtolower(str);
848                 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
849
850                 PlayerScoreField j;
851                 switch(str)
852                 {
853                         // fields without a label (not networked via the score system)
854                         case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
855                         case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
856                         case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
857                         case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
858                         case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
859                         case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
860                         case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
861                         default: // fields with a label
862                         {
863                                 // map alternative labels
864                                 if (str == "damage") str = "dmg";
865                                 if (str == "damagetaken") str = "dmgtaken";
866
867                                 FOREACH(Scores, true, {
868                                         if (str == strtolower(scores_label(it))) {
869                                                 j = it;
870                                                 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
871                                         }
872                                 });
873
874                                 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
875                                 if(!nocomplain && str != "fps") // server can disable the fps field
876                                         LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
877
878                                 strfree(sbt_field_title[sbt_num_fields]);
879                                 continue;
880
881                                 LABEL(found)
882                                 sbt_field[sbt_num_fields] = j;
883                                 if(j == ps_primary)
884                                         have_primary = true;
885                                 if(j == ps_secondary)
886                                         have_secondary = true;
887
888                         }
889                 }
890                 ++sbt_num_fields;
891                 if(sbt_num_fields >= MAX_SBT_FIELDS)
892                         break;
893         }
894
895         if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
896                 have_primary = true;
897         if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
898                 have_secondary = true;
899         if(ps_primary == ps_secondary)
900                 have_secondary = true;
901         missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
902
903         if(sbt_num_fields + missing < MAX_SBT_FIELDS)
904         {
905                 if(!have_name)
906                 {
907                         strfree(sbt_field_title[sbt_num_fields]);
908                         for(i = sbt_num_fields; i > 0; --i)
909                         {
910                                 sbt_field_title[i] = sbt_field_title[i-1];
911                                 sbt_field[i] = sbt_field[i-1];
912                         }
913                         sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
914                         sbt_field[0] = SP_NAME;
915                         ++sbt_num_fields;
916                         LOG_INFO("fixed missing field 'name'");
917
918                         if(!have_separator)
919                         {
920                                 strfree(sbt_field_title[sbt_num_fields]);
921                                 for(i = sbt_num_fields; i > 1; --i)
922                                 {
923                                         sbt_field_title[i] = sbt_field_title[i-1];
924                                         sbt_field[i] = sbt_field[i-1];
925                                 }
926                                 sbt_field_title[1] = strzone("|");
927                                 sbt_field[1] = SP_SEPARATOR;
928                                 ++sbt_num_fields;
929                                 LOG_INFO("fixed missing field '|'");
930                         }
931                 }
932                 else if(!have_separator)
933                 {
934                         strcpy(sbt_field_title[sbt_num_fields], "|");
935                         sbt_field[sbt_num_fields] = SP_SEPARATOR;
936                         ++sbt_num_fields;
937                         LOG_INFO("fixed missing field '|'");
938                 }
939                 if(!have_secondary)
940                 {
941                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
942                         sbt_field[sbt_num_fields] = ps_secondary;
943                         ++sbt_num_fields;
944                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
945                 }
946                 if(!have_primary)
947                 {
948                         strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
949                         sbt_field[sbt_num_fields] = ps_primary;
950                         ++sbt_num_fields;
951                         LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
952                 }
953         }
954
955         sbt_field[sbt_num_fields] = SP_END;
956         sb_field_sizes_init = 1;
957 }
958
959 string Scoreboard_AddPlayerId(string pl_name, entity pl)
960 {
961         string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
962         string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
963         return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
964 }
965
966 // MOVEUP::
967 vector sbt_field_rgb;
968 string sbt_field_icon0;
969 string sbt_field_icon1;
970 string sbt_field_icon2;
971 vector sbt_field_icon0_rgb;
972 vector sbt_field_icon1_rgb;
973 vector sbt_field_icon2_rgb;
974 string Scoreboard_GetName(entity pl)
975 {
976         if(ready_waiting && pl.ready)
977         {
978                 sbt_field_icon0 = "gfx/scoreboard/player_ready";
979         }
980         else if(!teamplay)
981         {
982                 int f;
983                 // NOTE: always adding 1024 allows saving the colormap 0 as a value != 0
984                 if (playerslots[pl.sv_entnum].colormap >= 1024)
985                         f = playerslots[pl.sv_entnum].colormap - 1024; // override server-side player colors
986                 else
987                         f = entcs_GetClientColors(pl.sv_entnum);
988
989                 {
990                         sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
991                         sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
992                         sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
993                         sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
994                         sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
995                 }
996         }
997         return entcs_GetName(pl.sv_entnum);
998 }
999
1000 int autocvar_hud_panel_scoreboard_ping_best = 0;
1001 int autocvar_hud_panel_scoreboard_ping_medium = 70;
1002 int autocvar_hud_panel_scoreboard_ping_high = 100;
1003 int autocvar_hud_panel_scoreboard_ping_worst = 150;
1004 vector autocvar_hud_panel_scoreboard_ping_best_color = '0 1 0';
1005 vector autocvar_hud_panel_scoreboard_ping_medium_color = '1 1 0';
1006 vector autocvar_hud_panel_scoreboard_ping_high_color = '1 0.5 0';
1007 vector autocvar_hud_panel_scoreboard_ping_worst_color = '1 0 0';
1008 #define PING_BEST autocvar_hud_panel_scoreboard_ping_best
1009 #define PING_MED autocvar_hud_panel_scoreboard_ping_medium
1010 #define PING_HIGH autocvar_hud_panel_scoreboard_ping_high
1011 #define PING_WORST autocvar_hud_panel_scoreboard_ping_worst
1012 #define COLOR_BEST autocvar_hud_panel_scoreboard_ping_best_color
1013 #define COLOR_MED autocvar_hud_panel_scoreboard_ping_medium_color
1014 #define COLOR_HIGH autocvar_hud_panel_scoreboard_ping_high_color
1015 #define COLOR_WORST autocvar_hud_panel_scoreboard_ping_worst_color
1016 string Scoreboard_GetField(entity pl, PlayerScoreField field, bool per_round)
1017 {
1018         float tmp, num, denom;
1019         int f;
1020         string str;
1021         sbt_field_rgb = '1 1 1';
1022         sbt_field_icon0 = "";
1023         sbt_field_icon1 = "";
1024         sbt_field_icon2 = "";
1025         sbt_field_icon0_rgb = '1 1 1';
1026         sbt_field_icon1_rgb = '1 1 1';
1027         sbt_field_icon2_rgb = '1 1 1';
1028         int rounds_played = 0;
1029         if (per_round)
1030                 rounds_played = pl.(scores(SP_ROUNDS_PL));
1031         switch(field)
1032         {
1033                 case SP_PING:
1034                         if (!pl.gotscores)
1035                                 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
1036                         //str = getplayerkeyvalue(pl.sv_entnum, "ping");
1037                         f = pl.ping;
1038                         if(f == 0)
1039                                 return _("N/A");
1040                         if(f < PING_BEST)
1041                                 sbt_field_rgb = COLOR_BEST;
1042                         else if(f < PING_MED)
1043                                 sbt_field_rgb = COLOR_BEST + (COLOR_MED - COLOR_BEST) * ((f - PING_BEST) / (PING_MED - PING_BEST));
1044                         else if(f < PING_HIGH)
1045                                 sbt_field_rgb = COLOR_MED + (COLOR_HIGH - COLOR_MED) * ((f - PING_MED) / (PING_HIGH - PING_MED));
1046                         else if(f < PING_WORST)
1047                                 sbt_field_rgb = COLOR_HIGH + (COLOR_WORST - COLOR_HIGH) * ((f - PING_HIGH) / (PING_WORST - PING_HIGH));
1048                         else
1049                                 sbt_field_rgb = COLOR_WORST;
1050                         return ftos(f);
1051
1052                 case SP_PL:
1053                         if (!pl.gotscores)
1054                                 return _("N/A");
1055                         f = pl.ping_packetloss;
1056                         tmp = pl.ping_movementloss;
1057                         if(f == 0 && tmp == 0)
1058                                 return "";
1059                         str = ftos(ceil(f * 100));
1060                         if(tmp != 0)
1061                                 str = strcat(str, "~", ftos(ceil(tmp * 100)));
1062                         tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
1063                         sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
1064                         return str;
1065
1066                 case SP_NAME:
1067                         str = Scoreboard_GetName(pl);
1068                         if (autocvar_hud_panel_scoreboard_playerid)
1069                                 str = Scoreboard_AddPlayerId(str, pl);
1070                         return str;
1071
1072                 case SP_FRAGS:
1073                         f = pl.(scores(SP_KILLS));
1074                         f -= pl.(scores(SP_SUICIDES));
1075                         if (rounds_played)
1076                                 return sprintf("%.1f", f / rounds_played);
1077                         return ftos(f);
1078
1079                 case SP_KDRATIO:
1080                         num = pl.(scores(SP_KILLS));
1081                         denom = pl.(scores(SP_DEATHS));
1082
1083                         if(denom == 0) {
1084                                 sbt_field_rgb = '0 1 0';
1085                                 if (rounds_played)
1086                                         str = sprintf("%.1f", num / rounds_played);
1087                                 else
1088                                         str = sprintf("%d", num);
1089                         } else if(num <= 0) {
1090                                 sbt_field_rgb = '1 0 0';
1091                                 if (rounds_played)
1092                                         str = sprintf("%.2f", num / (denom * rounds_played));
1093                                 else
1094                                         str = sprintf("%.1f", num / denom);
1095                         } else
1096                         {
1097                                 if (rounds_played)
1098                                         str = sprintf("%.2f", num / (denom * rounds_played));
1099                                 else
1100                                         str = sprintf("%.1f", num / denom);
1101                         }
1102                         return str;
1103
1104                 case SP_SUM:
1105                         f = pl.(scores(SP_KILLS));
1106                         f -= pl.(scores(SP_DEATHS));
1107
1108                         if(f > 0) {
1109                                 sbt_field_rgb = '0 1 0';
1110                         } else if(f == 0) {
1111                                 sbt_field_rgb = '1 1 1';
1112                         } else {
1113                                 sbt_field_rgb = '1 0 0';
1114                         }
1115                         if (rounds_played)
1116                                 return sprintf("%.1f", f / rounds_played);
1117                         return ftos(f);
1118
1119                 case SP_ELO:
1120                 {
1121                         float elo = pl.(scores(SP_ELO));
1122                         switch (elo) {
1123                                 case -1: return "...";
1124                                 case -2: return _("N/A");
1125                                 default: return ftos(elo);
1126                         }
1127                 }
1128
1129                 case SP_FPS:
1130                 {
1131                         float fps = pl.(scores(SP_FPS));
1132                         if(fps == 0)
1133                         {
1134                                 sbt_field_rgb = '1 1 1';
1135                                 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1136                         }
1137                         //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1138                         sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1139                         return ftos(fps);
1140                 }
1141
1142                 case SP_ROUNDS_PL:
1143                         return ftos(pl.(scores(field)));
1144
1145                 case SP_DMG: case SP_DMGTAKEN:
1146                         if (rounds_played)
1147                                 return sprintf("%.2f k", pl.(scores(field)) / (1000 * rounds_played));
1148                         return sprintf("%.1f k", pl.(scores(field)) / 1000);
1149
1150                 default: case SP_SCORE:
1151                         tmp = pl.(scores(field));
1152                         f = scores_flags(field);
1153                         if(field == ps_primary)
1154                                 sbt_field_rgb = '1 1 0';
1155                         else if(field == ps_secondary)
1156                                 sbt_field_rgb = '0 1 1';
1157                         else
1158                                 sbt_field_rgb = '1 1 1';
1159                         return ScoreString(f, tmp, rounds_played);
1160         }
1161         //return "error";
1162 }
1163
1164 float sbt_fixcolumnwidth_len;
1165 float sbt_fixcolumnwidth_iconlen;
1166 float sbt_fixcolumnwidth_marginlen;
1167
1168 string Scoreboard_FixColumnWidth(int i, string str, bool init)
1169 {
1170         TC(int, i);
1171         float f;
1172         vector sz;
1173
1174         sbt_fixcolumnwidth_iconlen = 0;
1175
1176         if(sbt_field_icon0 != "")
1177         {
1178                 sz = draw_getimagesize(sbt_field_icon0);
1179                 f = sz.x / sz.y;
1180                 if(sbt_fixcolumnwidth_iconlen < f)
1181                         sbt_fixcolumnwidth_iconlen = f;
1182         }
1183
1184         if(sbt_field_icon1 != "")
1185         {
1186                 sz = draw_getimagesize(sbt_field_icon1);
1187                 f = sz.x / sz.y;
1188                 if(sbt_fixcolumnwidth_iconlen < f)
1189                         sbt_fixcolumnwidth_iconlen = f;
1190         }
1191
1192         if(sbt_field_icon2 != "")
1193         {
1194                 sz = draw_getimagesize(sbt_field_icon2);
1195                 f = sz.x / sz.y;
1196                 if(sbt_fixcolumnwidth_iconlen < f)
1197                         sbt_fixcolumnwidth_iconlen = f;
1198         }
1199
1200         if(sbt_fixcolumnwidth_iconlen != 0)
1201         {
1202                 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1203                 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1204         }
1205         else
1206                 sbt_fixcolumnwidth_marginlen = 0;
1207
1208         if (init)
1209                 sbt_field_title_width[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1210
1211         if(sbt_field[i] == SP_NAME) // name gets all remaining space
1212         {
1213                 int j;
1214                 float remaining_space = 0;
1215                 for(j = 0; j < sbt_num_fields; ++j)
1216                         if(j != i)
1217                                 if (sbt_field[i] != SP_SEPARATOR)
1218                                         remaining_space += sbt_field_size[j] + hud_fontsize.x;
1219                 sbt_field_size[i] = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1220
1221                 if (sbt_fixcolumnwidth_iconlen != 0)
1222                         remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1223                 float namesize = max(sbt_field_title_width[i], panel_size.x - remaining_space);
1224                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1225                 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1226
1227                 max_namesize = max(sbt_field_title_width[i], vid_conwidth - remaining_space);
1228         }
1229         else
1230         {
1231                 if (init)
1232                 {
1233                         sbt_field_size[i] = sbt_field_title_width[i];
1234                         float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1235                         if (sbt_field_size[i] && sbt_field_size[i] > title_maxwidth)
1236                                 sbt_field_size[i] = title_maxwidth;
1237                 }
1238                 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1239         }
1240
1241         f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1242         if(sbt_field_size[i] < f)
1243                 sbt_field_size[i] = f;
1244
1245         sbt_field_title_condense_factor[i] = 0;
1246         if (sbt_field_title_width[i] > sbt_field_size[i])
1247         {
1248                 float real_maxwidth = sbt_field_size[i];
1249                 float title_maxwidth = sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor;
1250                 if (sbt_field_title_width[i] > title_maxwidth)
1251                         real_maxwidth = max(sbt_field_size[i], title_maxwidth);
1252                 sbt_field_title_condense_factor[i] = real_maxwidth / sbt_field_title_width[i];
1253         }
1254
1255         return str;
1256 }
1257
1258 void Scoreboard_initFieldSizes(bool compress_more)
1259 {
1260         if (compress_more)
1261         {
1262                 float sbt_field_title_maxwidth_factor_prev = sbt_field_title_maxwidth_factor;
1263                 sbt_field_title_maxwidth_factor -= 0.05;
1264                 if (sbt_field_title_maxwidth * sbt_field_title_maxwidth_factor < 0.01 * vid_conwidth)
1265                 {
1266                         sbt_field_title_maxwidth_factor = (0.01 * vid_conwidth) / sbt_field_title_maxwidth;
1267                         if (sbt_field_title_maxwidth_factor_prev == sbt_field_title_maxwidth_factor)
1268                                 return;
1269                 }
1270         }
1271         else
1272                 sbt_field_title_maxwidth_factor = 1;
1273
1274         for(int i = 0; i < sbt_num_fields; ++i)
1275         {
1276                 if (sbt_field[i] == SP_NAME)
1277                 {
1278                         name_field_index = i;
1279                         continue;
1280                 }
1281
1282                 Scoreboard_FixColumnWidth(i, "", true);
1283         }
1284
1285         // update name field size in the end as it takes remaining space
1286         Scoreboard_FixColumnWidth(name_field_index, "", true);
1287 }
1288
1289 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1290 {
1291         int i;
1292         vector column_dim = eY * panel_size.y;
1293         if(other_players)
1294                 column_dim.y -= 1.25 * hud_fontsize.y;
1295         vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1296         pos.x += hud_fontsize.x * 0.5;
1297         for(i = 0; i < sbt_num_fields; ++i)
1298         {
1299                 if(sbt_field[i] == SP_SEPARATOR)
1300                         break;
1301                 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1302                 if (sbt_highlight)
1303                         if (i % 2)
1304                                 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1305                 vector prev_drawfontscale = drawfontscale;
1306                 if (sbt_field_title_condense_factor[i])
1307                         drawfontscale.x *= sbt_field_title_condense_factor[i];
1308                 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1309                 if (sbt_field_title_condense_factor[i])
1310                 {
1311                         drawfontscale.x *= sbt_field_title_condense_factor[i];
1312                         drawfontscale = prev_drawfontscale;
1313                 }
1314                 pos.x += column_dim.x;
1315         }
1316
1317         if(sbt_field[i] == SP_SEPARATOR)
1318         {
1319                 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1320                 for(i = sbt_num_fields - 1; i > 0; --i)
1321                 {
1322                         if(sbt_field[i] == SP_SEPARATOR)
1323                                 break;
1324
1325                         pos.x -= sbt_field_size[i];
1326
1327                         if (sbt_highlight)
1328                                 if (!(i % 2))
1329                                 {
1330                                         column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1331                                         drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1332                                 }
1333
1334                         vector prev_drawfontscale = drawfontscale;
1335                         float titlewidth = stringwidth(sbt_field_title[i], false, hud_fontsize);
1336                         if (sbt_field_title_condense_factor[i])
1337                         {
1338                                 drawfontscale.x *= sbt_field_title_condense_factor[i];
1339                                 text_offset.x = sbt_field_size[i] - titlewidth * sbt_field_title_condense_factor[i];
1340                         }
1341                         else
1342                                 text_offset.x = sbt_field_size[i] - titlewidth;
1343                         drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1344                         if (sbt_field_title_condense_factor[i])
1345                         {
1346                                 drawfontscale.x *= sbt_field_title_condense_factor[i];
1347                                 drawfontscale = prev_drawfontscale;
1348                         }
1349                         pos.x -= hud_fontsize.x;
1350                 }
1351         }
1352
1353         pos.x = panel_pos.x;
1354         pos.y += 1.25 * hud_fontsize.y;
1355         return pos;
1356 }
1357
1358 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1359 {
1360         TC(bool, is_self); TC(int, pl_number);
1361         string str;
1362         bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1363
1364         vector h_pos = item_pos;
1365         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1366         // alternated rows highlighting
1367         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1368         {
1369                 if (pl == scoreboard_selected_player)
1370                         drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1371         }
1372         else if(is_self)
1373                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1374         else if((sbt_highlight) && (!(pl_number % 2)))
1375                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1376
1377         float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1378
1379         vector pos = item_pos;
1380         // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1381         if (is_self)
1382                 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1383
1384         pos.x += hud_fontsize.x * 0.5;
1385         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1386         vector tmp = '0 0 0';
1387         int i;
1388         PlayerScoreField field;
1389         for(i = 0; i < sbt_num_fields; ++i)
1390         {
1391                 field = sbt_field[i];
1392                 if(field == SP_SEPARATOR)
1393                         break;
1394
1395                 if(is_spec && field != SP_NAME && field != SP_PING) {
1396                         pos.x += sbt_field_size[i] + hud_fontsize.x;
1397                         continue;
1398                 }
1399                 str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1400                 str = Scoreboard_FixColumnWidth(i, str, false);
1401
1402                 pos.x += sbt_field_size[i] + hud_fontsize.x;
1403
1404                 if(field == SP_NAME) {
1405                         tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1406                         drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1407                 } else {
1408                         tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1409                         drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1410                 }
1411
1412                 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1413                 if(sbt_field_icon0 != "")
1414                         drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1415                 if(sbt_field_icon1 != "")
1416                         drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1417                 if(sbt_field_icon2 != "")
1418                         drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1419         }
1420
1421         if(sbt_field[i] == SP_SEPARATOR)
1422         {
1423                 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1424                 for(i = sbt_num_fields-1; i > 0; --i)
1425                 {
1426                         field = sbt_field[i];
1427                         if(field == SP_SEPARATOR)
1428                                 break;
1429
1430                         if(is_spec && field != SP_NAME && field != SP_PING) {
1431                                 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1432                                 continue;
1433                         }
1434
1435                         str = Scoreboard_GetField(pl, field, autocvar_hud_panel_scoreboard_scores_per_round);
1436                         str = Scoreboard_FixColumnWidth(i, str, false);
1437
1438                         if(field == SP_NAME) {
1439                                 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1440                                 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1441                         } else {
1442                                 tmp.x = sbt_fixcolumnwidth_len;
1443                                 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1444                         }
1445
1446                         tmp.x = sbt_field_size[i];
1447                         if(sbt_field_icon0 != "")
1448                                 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1449                         if(sbt_field_icon1 != "")
1450                                 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1451                         if(sbt_field_icon2 != "")
1452                                 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1453                         pos.x -= sbt_field_size[i] + hud_fontsize.x;
1454                 }
1455         }
1456
1457         if(pl.eliminated)
1458                 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1459 }
1460
1461 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1462 {
1463         int i = 0;
1464         vector h_pos = item_pos;
1465         vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1466
1467         bool complete = (this_team == NUM_SPECTATOR);
1468
1469         if(!complete)
1470         if((sbt_highlight) && (!(pl_number % 2)))
1471                 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1472
1473         vector pos = item_pos;
1474         pos.x += hud_fontsize.x * 0.5;
1475         pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1476
1477         float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1478         if(!complete)
1479                 width_limit -= stringwidth("...", false, hud_fontsize);
1480         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1481         static float max_name_width = 0;
1482         string field = "";
1483         float fieldsize = 0;
1484         float min_fieldsize = 0;
1485         float fieldpadding = hud_fontsize.x * 0.25;
1486         if(this_team == NUM_SPECTATOR)
1487         {
1488                 if(autocvar_hud_panel_scoreboard_spectators_showping)
1489                         min_fieldsize = stringwidth("999", false, hud_fontsize);
1490         }
1491         else if(autocvar_hud_panel_scoreboard_others_showscore)
1492                 min_fieldsize = stringwidth("99", false, hud_fontsize);
1493         for(i = 0; pl; pl = pl.sort_next)
1494         {
1495                 if(pl.team != this_team)
1496                         continue;
1497                 if(pl == ignored_pl)
1498                         continue;
1499
1500                 field = "";
1501                 if(this_team == NUM_SPECTATOR)
1502                 {
1503                         if(autocvar_hud_panel_scoreboard_spectators_showping)
1504                                 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1505                 }
1506                 else if(autocvar_hud_panel_scoreboard_others_showscore)
1507                         field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1508
1509                 string str = entcs_GetName(pl.sv_entnum);
1510                 if (autocvar_hud_panel_scoreboard_playerid)
1511                         str = Scoreboard_AddPlayerId(str, pl);
1512                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1513                 float column_width = stringwidth(str, true, hud_fontsize);
1514                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1515                 {
1516                         if(column_width > max_name_width)
1517                                 max_name_width = column_width;
1518                         column_width = max_name_width;
1519                 }
1520                 if(field != "")
1521                 {
1522                         fieldsize = stringwidth(field, false, hud_fontsize);
1523                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1524                 }
1525
1526                 if(pos.x + column_width > width_limit)
1527                 {
1528                         ++i;
1529                         if(!complete)
1530                         {
1531                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1532                                 break;
1533                         }
1534                         else
1535                         {
1536                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1537                                 pos.y += hud_fontsize.y * 1.25;
1538                         }
1539                 }
1540
1541                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1542                 {
1543                         if (pl == scoreboard_selected_player)
1544                         {
1545                                 h_size.x = column_width + hud_fontsize.x * 0.25;
1546                                 h_size.y = hud_fontsize.y;
1547                                 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1548                         }
1549                 }
1550
1551                 vector name_pos = pos;
1552                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1553                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1554                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1555                 if(field != "")
1556                 {
1557                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1558                         h_size.y = hud_fontsize.y;
1559                         vector field_pos = pos;
1560                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1561                                 field_pos.x += column_width - h_size.x;
1562                         if(sbt_highlight)
1563                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1564                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1565                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1566                 }
1567                 if(pl.eliminated)
1568                 {
1569                         h_size.x = column_width + hud_fontsize.x * 0.25;
1570                         h_size.y = hud_fontsize.y;
1571                         drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1572                 }
1573                 pos.x += column_width;
1574                 pos.x += hud_fontsize.x;
1575         }
1576         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1577 }
1578
1579 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1580 {
1581         int max_players = 999;
1582         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1583         {
1584                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1585                 if(teamplay)
1586                 {
1587                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1588                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1589                         height /= team_count;
1590                 }
1591                 else
1592                         height -= panel_bg_padding * 2; // - padding
1593                 max_players = floor(height / (hud_fontsize.y * 1.25));
1594                 if(max_players <= 1)
1595                         max_players = 1;
1596                 if(max_players == tm.team_size)
1597                         max_players = 999;
1598         }
1599
1600         entity pl;
1601         entity me = playerslots[current_player];
1602         panel_pos = pos;
1603         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1604         panel_size.y += panel_bg_padding * 2;
1605
1606         vector scoreboard_selected_hl_pos = pos;
1607         vector scoreboard_selected_hl_size = '0 0 0';
1608         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1609         scoreboard_selected_hl_size.y = panel_size.y;
1610
1611         HUD_Panel_DrawBg();
1612
1613         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1614         if(panel.current_panel_bg != "0")
1615                 end_pos.y += panel_bg_border * 2;
1616
1617         if(panel_bg_padding)
1618         {
1619                 panel_pos += '1 1 0' * panel_bg_padding;
1620                 panel_size -= '2 2 0' * panel_bg_padding;
1621         }
1622
1623         pos = panel_pos;
1624         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1625
1626         // rounded header
1627         if (sbt_bg_alpha)
1628                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1629
1630         pos.y += 1.25 * hud_fontsize.y;
1631
1632         // table background
1633         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1634         if (sbt_bg_alpha)
1635                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1636
1637
1638         // print header row and highlight columns
1639         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1640
1641         // fill the table and draw the rows
1642         bool is_self = false;
1643         bool self_shown = false;
1644         int i = 0;
1645         for(pl = players.sort_next; pl; pl = pl.sort_next)
1646         {
1647                 if(pl.team != tm.team)
1648                         continue;
1649                 if(i == max_players - 2 && pl != me)
1650                 {
1651                         if(!self_shown && me.team == tm.team)
1652                         {
1653                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1654                                 self_shown = true;
1655                                 pos.y += 1.25 * hud_fontsize.y;
1656                                 ++i;
1657                         }
1658                 }
1659                 if(i >= max_players - 1)
1660                 {
1661                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1662                         break;
1663                 }
1664                 is_self = (pl.sv_entnum == current_player);
1665                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1666                 if(is_self)
1667                         self_shown = true;
1668                 pos.y += 1.25 * hud_fontsize.y;
1669                 ++i;
1670         }
1671
1672         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1673         {
1674                 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1675                 {
1676                         float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1677                         _alpha *= panel_fg_alpha;
1678                         if (_alpha)
1679                                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1680                 }
1681         }
1682
1683         panel_size.x += panel_bg_padding * 2; // restore initial width
1684         return end_pos;
1685 }
1686
1687 bool Scoreboard_WouldDraw()
1688 {
1689         if (scoreboard_ui_enabled)
1690         {
1691                 if (scoreboard_ui_disabling)
1692                 {
1693                         if (scoreboard_fade_alpha == 0)
1694                                 HUD_Scoreboard_UI_Disable_Instantly();
1695                         return false;
1696                 }
1697                 if (intermission && scoreboard_ui_enabled == 2)
1698                 {
1699                         HUD_Scoreboard_UI_Disable_Instantly();
1700                         return false;
1701                 }
1702                 return true;
1703         }
1704         else if (MUTATOR_CALLHOOK(DrawScoreboard))
1705                 return false;
1706         else if (QuickMenu_IsOpened())
1707                 return false;
1708         else if (HUD_Radar_Clickable())
1709                 return false;
1710         else if (sb_showscores) // set by +showscores engine command
1711                 return true;
1712         else if (intermission == 1)
1713                 return true;
1714         else if (intermission == 2)
1715                 return false;
1716         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1717                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1718         {
1719                 return true;
1720         }
1721         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1722                 return true;
1723         return false;
1724 }
1725
1726 float average_accuracy;
1727 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1728 {
1729         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1730
1731         WepSet weapons_stat = WepSet_GetFromStat();
1732         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1733         int disownedcnt = 0;
1734         int nHidden = 0;
1735         FOREACH(Weapons, it != WEP_Null, {
1736                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1737
1738                 WepSet set = it.m_wepset;
1739                 if(it.spawnflags & WEP_TYPE_OTHER)
1740                 {
1741                         ++nHidden;
1742                         continue;
1743                 }
1744                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1745                 {
1746                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1747                                 ++nHidden;
1748                         else
1749                                 ++disownedcnt;
1750                 }
1751         });
1752
1753         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1754         if (weapon_cnt <= 0) return pos;
1755
1756         int rows = 1;
1757         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1758                 rows = 2;
1759         int columns = ceil(weapon_cnt / rows);
1760
1761         float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1762         float weapon_height = hud_fontsize.y * 2.3 / aspect;
1763         float height = weapon_height + hud_fontsize.y;
1764
1765         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);
1766         pos.y += 1.25 * hud_fontsize.y;
1767         if(panel.current_panel_bg != "0")
1768                 pos.y += panel_bg_border;
1769
1770         panel_pos = pos;
1771         panel_size.y = height * rows;
1772         panel_size.y += panel_bg_padding * 2;
1773
1774         float panel_bg_alpha_save = panel_bg_alpha;
1775         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1776         HUD_Panel_DrawBg();
1777         panel_bg_alpha = panel_bg_alpha_save;
1778
1779         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1780         if(panel.current_panel_bg != "0")
1781                 end_pos.y += panel_bg_border * 2;
1782
1783         if(panel_bg_padding)
1784         {
1785                 panel_pos += '1 1 0' * panel_bg_padding;
1786                 panel_size -= '2 2 0' * panel_bg_padding;
1787         }
1788
1789         pos = panel_pos;
1790         vector tmp = panel_size;
1791
1792         float weapon_width = tmp.x / columns / rows;
1793
1794         if (sbt_bg_alpha)
1795                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1796
1797         if(sbt_highlight)
1798         {
1799                 // column highlighting
1800                 for (int i = 0; i < columns; ++i)
1801                         if ((i % 2) == 0)
1802                                 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);
1803
1804                 // row highlighting
1805                 for (int i = 0; i < rows; ++i)
1806                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1807         }
1808
1809         average_accuracy = 0;
1810         int weapons_with_stats = 0;
1811         if (rows == 2)
1812                 pos.x += weapon_width / 2;
1813
1814         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1815                 rgb = '1 1 1';
1816         else
1817                 Accuracy_LoadColors();
1818
1819         float oldposx = pos.x;
1820         vector tmpos = pos;
1821
1822         int column = 0;
1823         FOREACH(Weapons, it != WEP_Null, {
1824                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1825
1826                 WepSet set = it.m_wepset;
1827                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1828                         continue;
1829                 if (it.spawnflags & WEP_TYPE_OTHER)
1830                         continue;
1831
1832                 float weapon_alpha;
1833                 if (weapon_stats >= 0)
1834                         weapon_alpha = sbt_fg_alpha;
1835                 else
1836                         weapon_alpha = 0.2 * sbt_fg_alpha;
1837
1838                 // weapon icon
1839                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1840                 // the accuracy
1841                 if (weapon_stats >= 0) {
1842                         weapons_with_stats += 1;
1843                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1844
1845                         string s = sprintf("%d%%", weapon_stats * 100);
1846                         float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1847
1848                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1849                                 rgb = Accuracy_GetColor(weapon_stats);
1850
1851                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1852                 }
1853                 tmpos.x += weapon_width * rows;
1854                 pos.x += weapon_width * rows;
1855                 if (rows == 2 && column == columns - 1) {
1856                         tmpos.x = oldposx;
1857                         tmpos.y += height;
1858                         pos.y += height;
1859                 }
1860                 ++column;
1861         });
1862
1863         if (weapons_with_stats)
1864                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1865
1866         panel_size.x += panel_bg_padding * 2; // restore initial width
1867
1868         return end_pos;
1869 }
1870
1871 bool is_item_filtered(entity it)
1872 {
1873         if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1874                 return false;
1875         int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1876         if (mask <= 0)
1877                 return false;
1878         if (it.instanceOfArmor || it.instanceOfHealth)
1879         {
1880                 int ha_mask = floor(mask) % 10;
1881                 switch (ha_mask)
1882                 {
1883                         default: return false;
1884                         case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1885                         case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1886                         case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1887                         case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1888                 }
1889         }
1890         if (it.instanceOfAmmo)
1891         {
1892                 int ammo_mask = floor(mask / 10) % 10;
1893                 return (ammo_mask == 1);
1894         }
1895         return false;
1896 }
1897
1898 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1899 {
1900         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1901
1902         int disowned_cnt = 0;
1903         int uninteresting_cnt = 0;
1904         IL_EACH(default_order_items, true, {
1905                 int q = g_inventory.inv_items[it.m_id];
1906                 //q = 1; // debug: display all items
1907                 if (is_item_filtered(it))
1908                         ++uninteresting_cnt;
1909                 else if (!q)
1910                         ++disowned_cnt;
1911         });
1912         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1913         int n = items_cnt - disowned_cnt;
1914         if (n <= 0) return pos;
1915
1916         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1917         int columns = max(6, ceil(n / rows));
1918
1919         float item_height = hud_fontsize.y * 2.3;
1920         float height = item_height + hud_fontsize.y;
1921
1922         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1923         pos.y += 1.25 * hud_fontsize.y;
1924         if(panel.current_panel_bg != "0")
1925                 pos.y += panel_bg_border;
1926
1927         panel_pos = pos;
1928         panel_size.y = height * rows;
1929         panel_size.y += panel_bg_padding * 2;
1930
1931         float panel_bg_alpha_save = panel_bg_alpha;
1932         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1933         HUD_Panel_DrawBg();
1934         panel_bg_alpha = panel_bg_alpha_save;
1935
1936         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1937         if(panel.current_panel_bg != "0")
1938                 end_pos.y += panel_bg_border * 2;
1939
1940         if(panel_bg_padding)
1941         {
1942                 panel_pos += '1 1 0' * panel_bg_padding;
1943                 panel_size -= '2 2 0' * panel_bg_padding;
1944         }
1945
1946         pos = panel_pos;
1947         vector tmp = panel_size;
1948
1949         float item_width = tmp.x / columns / rows;
1950
1951         if (sbt_bg_alpha)
1952                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1953
1954         if(sbt_highlight)
1955         {
1956                 // column highlighting
1957                 for (int i = 0; i < columns; ++i)
1958                         if ((i % 2) == 0)
1959                                 drawfill(pos + eX * item_width * rows * i, vec2(item_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1960
1961                 // row highlighting
1962                 for (int i = 0; i < rows; ++i)
1963                         drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1964         }
1965
1966         if (rows == 2)
1967                 pos.x += item_width / 2;
1968
1969         float oldposx = pos.x;
1970         vector tmpos = pos;
1971
1972         int column = 0;
1973         IL_EACH(default_order_items, !is_item_filtered(it), {
1974                 int n = g_inventory.inv_items[it.m_id];
1975                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1976                 if (n <= 0) continue;
1977                 drawpic_aspect_skin(tmpos, it.m_icon, eX * item_width + eY * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1978                 string s = ftos(n);
1979                 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1980                 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1981                 tmpos.x += item_width * rows;
1982                 pos.x += item_width * rows;
1983                 if (rows == 2 && column == columns - 1) {
1984                         tmpos.x = oldposx;
1985                         tmpos.y += height;
1986                         pos.y += height;
1987                 }
1988                 ++column;
1989         });
1990
1991         panel_size.x += panel_bg_padding * 2; // restore initial width
1992
1993         return end_pos;
1994 }
1995
1996 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1997         float px = pos.x;
1998         pos.x += hud_fontsize.x * 0.25;
1999         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2000         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
2001         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2002         pos.x = px;
2003         pos.y += hud_fontsize.y;
2004
2005         return pos;
2006 }
2007
2008 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
2009         float stat_secrets_found, stat_secrets_total;
2010         float stat_monsters_killed, stat_monsters_total;
2011         float rows = 0;
2012         string val;
2013
2014         // get monster stats
2015         stat_monsters_killed = STAT(MONSTERS_KILLED);
2016         stat_monsters_total = STAT(MONSTERS_TOTAL);
2017
2018         // get secrets stats
2019         stat_secrets_found = STAT(SECRETS_FOUND);
2020         stat_secrets_total = STAT(SECRETS_TOTAL);
2021
2022         // get number of rows
2023         if(stat_secrets_total)
2024                 rows += 1;
2025         if(stat_monsters_total)
2026                 rows += 1;
2027
2028         // if no rows, return
2029         if (!rows)
2030                 return pos;
2031
2032         //  draw table header
2033         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2034         pos.y += 1.25 * hud_fontsize.y;
2035         if(panel.current_panel_bg != "0")
2036                 pos.y += panel_bg_border;
2037
2038         panel_pos = pos;
2039         panel_size.y = hud_fontsize.y * rows;
2040         panel_size.y += panel_bg_padding * 2;
2041         HUD_Panel_DrawBg();
2042
2043         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2044         if(panel.current_panel_bg != "0")
2045                 end_pos.y += panel_bg_border * 2;
2046
2047         if(panel_bg_padding)
2048         {
2049                 panel_pos += '1 1 0' * panel_bg_padding;
2050                 panel_size -= '2 2 0' * panel_bg_padding;
2051         }
2052
2053         pos = panel_pos;
2054         vector tmp = panel_size;
2055
2056         if (sbt_bg_alpha)
2057                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2058
2059         // draw monsters
2060         if(stat_monsters_total)
2061         {
2062                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2063                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2064         }
2065
2066         // draw secrets
2067         if(stat_secrets_total)
2068         {
2069                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2070                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2071         }
2072
2073         panel_size.x += panel_bg_padding * 2; // restore initial width
2074         return end_pos;
2075 }
2076
2077 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2078 {
2079         int i;
2080         RANKINGS_RECEIVED_CNT = 0;
2081         for (i=RANKINGS_CNT-1; i>=0; --i)
2082                 if (grecordtime[i])
2083                         ++RANKINGS_RECEIVED_CNT;
2084
2085         if (RANKINGS_RECEIVED_CNT == 0)
2086                 return pos;
2087
2088         vector hl_rgb = rgb + '0.5 0.5 0.5';
2089
2090         vector scoreboard_selected_hl_pos = pos;
2091
2092         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2093         pos.y += 1.25 * hud_fontsize.y;
2094         if(panel.current_panel_bg != "0")
2095                 pos.y += panel_bg_border;
2096
2097         vector scoreboard_selected_hl_size = '0 0 0';
2098         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2099         scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2100
2101         panel_pos = pos;
2102
2103         float namesize = 0;
2104         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2105         {
2106                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2107                 if(f > namesize)
2108                         namesize = f;
2109         }
2110         bool cut = false;
2111         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2112         {
2113                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2114                 cut = true;
2115         }
2116
2117         float ranksize = 3 * hud_fontsize.x;
2118         float timesize = 5 * hud_fontsize.x;
2119         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2120         rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2121         rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2122         if (!rankings_cnt)
2123         {
2124                 rankings_cnt = RANKINGS_RECEIVED_CNT;
2125                 rankings_rows = ceil(rankings_cnt / rankings_columns);
2126         }
2127
2128         // expand name column to fill the entire row
2129         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2130         namesize += available_space;
2131         columnsize.x += available_space;
2132
2133         panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2134         panel_size.y += panel_bg_padding * 2;
2135         scoreboard_selected_hl_size.y += panel_size.y;
2136
2137         HUD_Panel_DrawBg();
2138
2139         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2140         if(panel.current_panel_bg != "0")
2141                 end_pos.y += panel_bg_border * 2;
2142
2143         if(panel_bg_padding)
2144         {
2145                 panel_pos += '1 1 0' * panel_bg_padding;
2146                 panel_size -= '2 2 0' * panel_bg_padding;
2147         }
2148
2149         pos = panel_pos;
2150
2151         if (sbt_bg_alpha)
2152                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2153
2154         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2155         string str = "";
2156         int column = 0, j = 0;
2157         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2158         int start_item = rankings_start_column * rankings_rows;
2159         for(i = start_item; i < start_item + rankings_cnt; ++i)
2160         {
2161                 int t = grecordtime[i];
2162                 if (t == 0)
2163                         continue;
2164
2165                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2166                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2167                 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2168                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2169
2170                 str = count_ordinal(i+1);
2171                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2172                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2173                 str = ColorTranslateRGB(grecordholder[i]);
2174                 if(cut)
2175                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2176                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2177
2178                 pos.y += 1.25 * hud_fontsize.y;
2179                 j++;
2180                 if(j >= rankings_rows)
2181                 {
2182                         column++;
2183                         j = 0;
2184                         pos.x += panel_size.x / rankings_columns;
2185                         pos.y = panel_pos.y;
2186                 }
2187         }
2188         strfree(zoned_name_self);
2189
2190         if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2191         {
2192                 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2193                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2194         }
2195
2196         panel_size.x += panel_bg_padding * 2; // restore initial width
2197         return end_pos;
2198 }
2199
2200 bool have_weapon_stats;
2201 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2202 {
2203         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2204                 return false;
2205         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2206                 return false;
2207
2208         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2209                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2210                 && !intermission)
2211         {
2212                 return false;
2213         }
2214
2215         if (!have_weapon_stats)
2216         {
2217                 FOREACH(Weapons, it != WEP_Null, {
2218                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2219                         if (weapon_stats >= 0)
2220                         {
2221                                 have_weapon_stats = true;
2222                                 break;
2223                         }
2224                 });
2225                 if (!have_weapon_stats)
2226                         return false;
2227         }
2228
2229         return true;
2230 }
2231
2232 bool have_item_stats;
2233 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2234 {
2235         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2236                 return false;
2237         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2238                 return false;
2239
2240         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2241                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2242                 && !intermission)
2243         {
2244                 return false;
2245         }
2246
2247         if (!have_item_stats)
2248         {
2249                 IL_EACH(default_order_items, true, {
2250                         if (!is_item_filtered(it))
2251                         {
2252                                 int q = g_inventory.inv_items[it.m_id];
2253                                 //q = 1; // debug: display all items
2254                                 if (q)
2255                                 {
2256                                         have_item_stats = true;
2257                                         break;
2258                                 }
2259                         }
2260                 });
2261                 if (!have_item_stats)
2262                         return false;
2263         }
2264
2265         return true;
2266 }
2267
2268 vector Scoreboard_Spectators_Draw(vector pos) {
2269
2270         entity pl, tm;
2271         string str = "";
2272
2273         for(pl = players.sort_next; pl; pl = pl.sort_next)
2274         {
2275                 if(pl.team == NUM_SPECTATOR)
2276                 {
2277                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2278                                 if(tm.team == NUM_SPECTATOR)
2279                                         break;
2280                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2281                         draw_beginBoldFont();
2282                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2283                         draw_endBoldFont();
2284                         pos.y += 1.25 * hud_fontsize.y;
2285
2286                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2287                         pos.y += 1.25 * hud_fontsize.y;
2288
2289                         break;
2290                 }
2291         }
2292         if (str != "") // if there's at least one spectator
2293                 pos.y += 0.5 * hud_fontsize.y;
2294
2295         return pos;
2296 }
2297
2298 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2299 {
2300         string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2301         int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2302         return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2303                 (s_label == "score") ? CTX(_("SCO^points")) :
2304                 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2305 }
2306
2307 void Scoreboard_Draw()
2308 {
2309         if(!autocvar__hud_configure)
2310         {
2311                 if(!hud_draw_maximized) return;
2312
2313                 // frametime checks allow to toggle the scoreboard even when the game is paused
2314                 if(scoreboard_active) {
2315                         if (scoreboard_fade_alpha == 0)
2316                                 scoreboard_time = time;
2317                         if(hud_configure_menu_open == 1)
2318                                 scoreboard_fade_alpha = 1;
2319                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2320                         if (scoreboard_fadeinspeed && frametime)
2321                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2322                         else
2323                                 scoreboard_fade_alpha = 1;
2324
2325                         static string hud_fontsize_str;
2326                         if(hud_fontsize_str != autocvar_hud_fontsize)
2327                         {
2328                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2329                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2330                                 sb_field_sizes_init = 1;
2331                         }
2332
2333                         static float scoreboard_table_fieldtitle_maxwidth_prev;
2334                         if (scoreboard_table_fieldtitle_maxwidth_prev != autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth)
2335                         {
2336                                 scoreboard_table_fieldtitle_maxwidth_prev = autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth;
2337                                 sbt_field_title_maxwidth = bound(0.01, autocvar_hud_panel_scoreboard_table_fieldtitle_maxwidth, 0.1);
2338                                 sbt_field_title_maxwidth *= vid_conwidth;
2339                                 sb_field_sizes_init = 1;
2340                         }
2341                 }
2342                 else {
2343                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2344                         if (scoreboard_fadeoutspeed && frametime)
2345                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2346                         else
2347                                 scoreboard_fade_alpha = 0;
2348                 }
2349
2350                 if (!scoreboard_fade_alpha)
2351                 {
2352                         scoreboard_acc_fade_alpha = 0;
2353                         scoreboard_itemstats_fade_alpha = 0;
2354                         return;
2355                 }
2356         }
2357         else
2358                 scoreboard_fade_alpha = 0;
2359
2360         if (autocvar_hud_panel_scoreboard_dynamichud)
2361                 HUD_Scale_Enable();
2362         else
2363                 HUD_Scale_Disable();
2364
2365         if(scoreboard_fade_alpha <= 0)
2366                 return;
2367         panel_fade_alpha *= scoreboard_fade_alpha;
2368         HUD_Panel_LoadCvars();
2369
2370         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2371         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2372         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2373         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2374         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2375         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2376         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2377
2378         // don't overlap with con_notify
2379         if(!autocvar__hud_configure)
2380                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2381
2382         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2383         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2384         scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2385         scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2386         panel_pos.x = scoreboard_left;
2387         panel_size.x = fixed_scoreboard_width;
2388
2389         // field sizes can be initialized now after panel_size.x calculation
2390         if (!sbt_field_size[0] || sb_field_sizes_init)
2391         {
2392                 bool compress = (sb_field_sizes_init == 2);
2393                 Scoreboard_initFieldSizes(compress);
2394                 sb_field_sizes_init = 0;
2395         }
2396
2397         Scoreboard_UpdatePlayerTeams();
2398
2399         scoreboard_top = panel_pos.y;
2400         vector pos = panel_pos;
2401         entity tm;
2402         string str;
2403         vector str_pos;
2404
2405         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2406
2407         // Begin of Game Info Section
2408         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2409         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2410
2411         // Game Info: Game Type
2412         if (scoreboard_ui_enabled == 2)
2413                 str = _("Team Selection");
2414         else if (gametype_custom_name != "")
2415                 str = gametype_custom_name;
2416         else
2417                 str = MapInfo_Type_ToText(gametype);
2418         draw_beginBoldFont();
2419         drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_type_fontsize)), str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2420         draw_endBoldFont();
2421
2422         pos.y += sb_gameinfo_type_fontsize.y;
2423         // Game Info: Game Detail
2424         if (scoreboard_ui_enabled == 2)
2425         {
2426                 if (scoreboard_selected_team)
2427                         str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2428                 else
2429                         str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2430                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2431
2432                 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2433                 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2434                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2435         }
2436         else
2437         {
2438                 float tl = STAT(TIMELIMIT);
2439                 float fl = STAT(FRAGLIMIT);
2440                 float ll = STAT(LEADLIMIT);
2441                 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2442                 str = "";
2443                 if(tl > 0)
2444                         str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2445                 if(!gametype.m_hidelimits)
2446                 {
2447                         if(fl > 0)
2448                         {
2449                                 if(tl > 0)
2450                                         str = strcat(str, "^7 / "); // delimiter
2451                                 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2452                         }
2453                         if(ll > 0)
2454                         {
2455                                 if(tl > 0 || fl > 0)
2456                                 {
2457                                         // delimiter
2458                                         if (ll_and_fl && fl > 0)
2459                                                 str = strcat(str, "^7 & ");
2460                                         else
2461                                                 str = strcat(str, "^7 / ");
2462                                 }
2463                                 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2464                         }
2465                 }
2466                 drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
2467                 // map name and player count
2468                 if (campaign)
2469                         str = "";
2470                 else
2471                         str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2472                 str = strcat("^7", _("Map:"), " ^2", mi_shortname, "    ", str); // reusing "Map:" translatable string
2473                 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2474         }
2475         // End of Game Info Section
2476
2477         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2478         if(panel.current_panel_bg != "0")
2479                 pos.y += panel_bg_border;
2480
2481         // Draw the scoreboard
2482         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2483         if(scale <= 0)
2484                 scale = 0.25;
2485         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2486
2487         if(teamplay)
2488         {
2489                 vector panel_bg_color_save = panel_bg_color;
2490                 vector team_score_baseoffset;
2491                 vector team_size_baseoffset;
2492                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2493                 {
2494                         // put team score to the left of scoreboard (and team size to the right)
2495                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2496                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2497                         if(panel.current_panel_bg != "0")
2498                         {
2499                                 team_score_baseoffset.x -= panel_bg_border;
2500                                 team_size_baseoffset.x += panel_bg_border;
2501                         }
2502                 }
2503                 else
2504                 {
2505                         // put team score to the right of scoreboard (and team size to the left)
2506                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2507                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2508                         if(panel.current_panel_bg != "0")
2509                         {
2510                                 team_score_baseoffset.x += panel_bg_border;
2511                                 team_size_baseoffset.x -= panel_bg_border;
2512                         }
2513                 }
2514
2515                 int team_size_total = 0;
2516                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2517                 {
2518                         // calculate team size total (sum of all team sizes)
2519                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2520                                 if(tm.team != NUM_SPECTATOR)
2521                                         team_size_total += tm.team_size;
2522                 }
2523
2524                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2525                 {
2526                         if(tm.team == NUM_SPECTATOR)
2527                                 continue;
2528                         if(!tm.team)
2529                                 continue;
2530
2531                         draw_beginBoldFont();
2532                         vector rgb = Team_ColorRGB(tm.team);
2533                         str = ftos(tm.(teamscores(ts_primary)));
2534                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2535                         {
2536                                 // team score on the left (default)
2537                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2538                         }
2539                         else
2540                         {
2541                                 // team score on the right
2542                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2543                         }
2544                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2545
2546                         // team size (if set to show on the side)
2547                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2548                         {
2549                                 // calculate the starting position for the whole team size info string
2550                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
2551                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2552                                 {
2553                                         // team size on the left
2554                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2555                                 }
2556                                 else
2557                                 {
2558                                         // team size on the right
2559                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2560                                 }
2561                                 str = sprintf("%d", tm.team_size);
2562                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2563                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2564                                 str = sprintf("/%d", team_size_total);
2565                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2566                         }
2567
2568
2569                         // secondary score, e.g. keyhunt
2570                         if(ts_primary != ts_secondary)
2571                         {
2572                                 str = ftos(tm.(teamscores(ts_secondary)));
2573                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2574                                 {
2575                                         // left
2576                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2577                                 }
2578                                 else
2579                                 {
2580                                         // right
2581                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2582                                 }
2583
2584                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2585                         }
2586                         draw_endBoldFont();
2587                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2588                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2589                         else if(panel_bg_color_team > 0)
2590                                 panel_bg_color = rgb * panel_bg_color_team;
2591                         else
2592                                 panel_bg_color = rgb;
2593                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2594                 }
2595                 panel_bg_color = panel_bg_color_save;
2596         }
2597         else
2598         {
2599                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2600                         if(tm.team != NUM_SPECTATOR)
2601                                 break;
2602
2603                 // display it anyway
2604                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2605         }
2606
2607         // if the name column is too small, try to compress all other field titles
2608         if (sbt_field_size[name_field_index] < sbt_field_title_width[name_field_index] + hud_fontsize.x)
2609                 sb_field_sizes_init = 2;
2610
2611         // draw scoreboard spectators before accuracy and item stats
2612         if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2613                 pos = Scoreboard_Spectators_Draw(pos);
2614         }
2615
2616         // draw accuracy and item stats
2617         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2618                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2619         if (Scoreboard_ItemStats_WouldDraw(pos.y))
2620                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2621
2622         // draw scoreboard spectators after accuracy and item stats and before rankings
2623         if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2624                 pos = Scoreboard_Spectators_Draw(pos);
2625         }
2626
2627         if(MUTATOR_CALLHOOK(ShowRankings)) {
2628                 string ranktitle = M_ARGV(0, string);
2629                 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2630                 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2631                 if(race_speedaward_alltimebest)
2632                 {
2633                         string name;
2634                         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2635                         str = "";
2636                         if(race_speedaward)
2637                         {
2638                                 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2639                                 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2640                                 str = strcat(str, " / ");
2641                         }
2642                         name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2643                         str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2644                         drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2645                         pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2646                 }
2647                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2648         }
2649         else
2650                 rankings_cnt = 0;
2651
2652         // draw scoreboard spectators after rankings
2653         if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2654                 pos = Scoreboard_Spectators_Draw(pos);
2655         }
2656
2657         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2658
2659         // draw scoreboard spectators after mapstats
2660         if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2661                 pos = Scoreboard_Spectators_Draw(pos);
2662         }
2663
2664
2665         // print information about respawn status
2666         float respawn_time = STAT(RESPAWN_TIME);
2667         if(!intermission && respawn_time)
2668         {
2669                 if(respawn_time < 0)
2670                 {
2671                         // a negative number means we are awaiting respawn, time value is still the same
2672                         respawn_time *= -1; // remove mark now that we checked it
2673
2674                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2675                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2676                         else
2677                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2678                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2679                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2680                                                 :
2681                                                 count_seconds(ceil(respawn_time - time))
2682                                         )
2683                                 );
2684                 }
2685                 else if(time < respawn_time)
2686                 {
2687                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2688                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2689                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2690                                         :
2691                                         count_seconds(ceil(respawn_time - time))
2692                                 )
2693                         );
2694                 }
2695                 else if(time >= respawn_time)
2696                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2697
2698                 pos.y += 1.2 * hud_fontsize.y;
2699                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2700         }
2701
2702         pos.y += hud_fontsize.y;
2703         if (scoreboard_fade_alpha < 1)
2704                 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2705         else if (pos.y != scoreboard_bottom)
2706         {
2707                 if (pos.y > scoreboard_bottom)
2708                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2709                 else
2710                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2711         }
2712
2713         if (rankings_cnt)
2714         {
2715                 if (scoreboard_fade_alpha == 1)
2716                 {
2717                         if (scoreboard_bottom > 0.95 * vid_conheight)
2718                                 rankings_rows = max(1, rankings_rows - 1);
2719                         else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2720                                 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2721                 }
2722                 rankings_cnt = rankings_rows * rankings_columns;
2723         }
2724 }