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