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