]> git.xonotic.org Git - xonotic/xonotic-data.pk3dir.git/blob - qcsrc/client/hud/panel/scoreboard.qc
Merge branch 'master' into z411/team_queue
[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                 if(entcs_GetWantsJoin(pl.sv_entnum))
1435                 {
1436                         vector tmcolor = Team_ColorRGB(Team_IndexToTeam(entcs_GetWantsJoin(pl.sv_entnum)));
1437                         tmcolor -= tmcolor * sin(2*M_PI*time);
1438
1439                         drawstring(pos, "(Q)", hud_fontsize, tmcolor, sbt_fg_alpha, DRAWFLAG_NORMAL);
1440                         pos.x += stringwidth("(Q) ", true, hud_fontsize);
1441                 }
1442
1443                 field = "";
1444                 if(this_team == NUM_SPECTATOR)
1445                 {
1446                         if(autocvar_hud_panel_scoreboard_spectators_showping)
1447                                 field = Scoreboard_GetField(pl, SP_PING, autocvar_hud_panel_scoreboard_scores_per_round);
1448                 }
1449                 else if(autocvar_hud_panel_scoreboard_others_showscore)
1450                         field = Scoreboard_GetField(pl, SP_SCORE, autocvar_hud_panel_scoreboard_scores_per_round);
1451
1452                 string str = entcs_GetName(pl.sv_entnum);
1453                 if (autocvar_hud_panel_scoreboard_playerid)
1454                         str = Scoreboard_AddPlayerId(str, pl);
1455                 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1456                 float column_width = stringwidth(str, true, hud_fontsize);
1457                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1458                 {
1459                         if(column_width > max_name_width)
1460                                 max_name_width = column_width;
1461                         column_width = max_name_width;
1462                 }
1463                 if(field != "")
1464                 {
1465                         fieldsize = stringwidth(field, false, hud_fontsize);
1466                         column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1467                 }
1468
1469                 if(pos.x + column_width > width_limit)
1470                 {
1471                         ++i;
1472                         if(!complete)
1473                         {
1474                                 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1475                                 break;
1476                         }
1477                         else
1478                         {
1479                                 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1480                                 pos.y += hud_fontsize.y * 1.25;
1481                         }
1482                 }
1483
1484                 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1485                 {
1486                         if (pl == scoreboard_selected_player)
1487                         {
1488                                 h_size.x = column_width + hud_fontsize.x * 0.25;
1489                                 h_size.y = hud_fontsize.y;
1490                                 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1491                         }
1492                 }
1493
1494                 vector name_pos = pos;
1495                 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1496                         name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1497                 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1498                 if(field != "")
1499                 {
1500                         h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1501                         h_size.y = hud_fontsize.y;
1502                         vector field_pos = pos;
1503                         if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1504                                 field_pos.x += column_width - h_size.x;
1505                         if(sbt_highlight)
1506                                 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1507                         field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1508                         drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1509                 }
1510                 if(pl.eliminated)
1511                 {
1512                         h_size.x = column_width + hud_fontsize.x * 0.25;
1513                         h_size.y = hud_fontsize.y;
1514                         drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1515                 }
1516                 pos.x += column_width;
1517                 pos.x += hud_fontsize.x;
1518         }
1519         return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1520 }
1521
1522 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1523 {
1524         int max_players = 999;
1525         if(autocvar_hud_panel_scoreboard_maxheight > 0)
1526         {
1527                 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1528                 if(teamplay)
1529                 {
1530                         height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1531                         height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1532                         height /= team_count;
1533                 }
1534                 else
1535                         height -= panel_bg_padding * 2; // - padding
1536                 max_players = floor(height / (hud_fontsize.y * 1.25));
1537                 if(max_players <= 1)
1538                         max_players = 1;
1539                 if(max_players == tm.team_size)
1540                         max_players = 999;
1541         }
1542
1543         entity pl;
1544         entity me = playerslots[current_player];
1545         panel_pos = pos;
1546         panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1547         panel_size.y += panel_bg_padding * 2;
1548
1549         vector scoreboard_selected_hl_pos = pos;
1550         vector scoreboard_selected_hl_size = '0 0 0';
1551         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1552         scoreboard_selected_hl_size.y = panel_size.y;
1553
1554         HUD_Panel_DrawBg();
1555
1556         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1557         if(panel.current_panel_bg != "0")
1558                 end_pos.y += panel_bg_border * 2;
1559
1560         if(panel_bg_padding)
1561         {
1562                 panel_pos += '1 1 0' * panel_bg_padding;
1563                 panel_size -= '2 2 0' * panel_bg_padding;
1564         }
1565
1566         pos = panel_pos;
1567         vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1568
1569         // rounded header
1570         if (sbt_bg_alpha)
1571                 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1572
1573         pos.y += 1.25 * hud_fontsize.y;
1574
1575         // table background
1576         tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1577         if (sbt_bg_alpha)
1578                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1579
1580
1581         // print header row and highlight columns
1582         pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1583
1584         // fill the table and draw the rows
1585         bool is_self = false;
1586         bool self_shown = false;
1587         int i = 0;
1588         for(pl = players.sort_next; pl; pl = pl.sort_next)
1589         {
1590                 if(pl.team != tm.team)
1591                         continue;
1592                 if(i == max_players - 2 && pl != me)
1593                 {
1594                         if(!self_shown && me.team == tm.team)
1595                         {
1596                                 Scoreboard_DrawItem(pos, rgb, me, true, i);
1597                                 self_shown = true;
1598                                 pos.y += 1.25 * hud_fontsize.y;
1599                                 ++i;
1600                         }
1601                 }
1602                 if(i >= max_players - 1)
1603                 {
1604                         pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1605                         break;
1606                 }
1607                 is_self = (pl.sv_entnum == current_player);
1608                 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1609                 if(is_self)
1610                         self_shown = true;
1611                 pos.y += 1.25 * hud_fontsize.y;
1612                 ++i;
1613         }
1614
1615         if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1616         {
1617                 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1618                 {
1619                         float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1620                         _alpha *= panel_fg_alpha;
1621                         if (_alpha)
1622                                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1623                 }
1624         }
1625
1626         panel_size.x += panel_bg_padding * 2; // restore initial width
1627         return end_pos;
1628 }
1629
1630 bool Scoreboard_WouldDraw()
1631 {
1632         if (scoreboard_ui_enabled)
1633         {
1634                 if (scoreboard_ui_disabling)
1635                 {
1636                         if (scoreboard_fade_alpha == 0)
1637                                 HUD_Scoreboard_UI_Disable_Instantly();
1638                         return false;
1639                 }
1640                 if (intermission && scoreboard_ui_enabled == 2)
1641                 {
1642                         HUD_Scoreboard_UI_Disable_Instantly();
1643                         return false;
1644                 }
1645                 return true;
1646         }
1647         else if (MUTATOR_CALLHOOK(DrawScoreboard))
1648                 return false;
1649         else if (QuickMenu_IsOpened())
1650                 return false;
1651         else if (HUD_Radar_Clickable())
1652                 return false;
1653         else if (sb_showscores) // set by +showscores engine command
1654                 return true;
1655         else if (intermission == 1)
1656                 return true;
1657         else if (intermission == 2)
1658                 return false;
1659         else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1660                 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1661         {
1662                 return true;
1663         }
1664         else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1665                 return true;
1666         return false;
1667 }
1668
1669 float average_accuracy;
1670 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1671 {
1672         scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1673
1674         WepSet weapons_stat = WepSet_GetFromStat();
1675         WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1676         int disownedcnt = 0;
1677         int nHidden = 0;
1678         FOREACH(Weapons, it != WEP_Null, {
1679                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1680
1681                 WepSet set = it.m_wepset;
1682                 if(it.spawnflags & WEP_TYPE_OTHER)
1683                 {
1684                         ++nHidden;
1685                         continue;
1686                 }
1687                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1688                 {
1689                         if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1690                                 ++nHidden;
1691                         else
1692                                 ++disownedcnt;
1693                 }
1694         });
1695
1696         int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1697         if (weapon_cnt <= 0) return pos;
1698
1699         int rows = 1;
1700         if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1701                 rows = 2;
1702         int columns = ceil(weapon_cnt / rows);
1703
1704         float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1705         float weapon_height = hud_fontsize.y * 2.3 / aspect;
1706         float height = weapon_height + hud_fontsize.y;
1707
1708         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);
1709         pos.y += 1.25 * hud_fontsize.y;
1710         if(panel.current_panel_bg != "0")
1711                 pos.y += panel_bg_border;
1712
1713         panel_pos = pos;
1714         panel_size.y = height * rows;
1715         panel_size.y += panel_bg_padding * 2;
1716
1717         float panel_bg_alpha_save = panel_bg_alpha;
1718         panel_bg_alpha *= scoreboard_acc_fade_alpha;
1719         HUD_Panel_DrawBg();
1720         panel_bg_alpha = panel_bg_alpha_save;
1721
1722         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1723         if(panel.current_panel_bg != "0")
1724                 end_pos.y += panel_bg_border * 2;
1725
1726         if(panel_bg_padding)
1727         {
1728                 panel_pos += '1 1 0' * panel_bg_padding;
1729                 panel_size -= '2 2 0' * panel_bg_padding;
1730         }
1731
1732         pos = panel_pos;
1733         vector tmp = panel_size;
1734
1735         float weapon_width = tmp.x / columns / rows;
1736
1737         if (sbt_bg_alpha)
1738                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1739
1740         if(sbt_highlight)
1741         {
1742                 // column highlighting
1743                 for (int i = 0; i < columns; ++i)
1744                         if ((i % 2) == 0)
1745                                 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);
1746
1747                 // row highlighting
1748                 for (int i = 0; i < rows; ++i)
1749                         drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1750         }
1751
1752         average_accuracy = 0;
1753         int weapons_with_stats = 0;
1754         if (rows == 2)
1755                 pos.x += weapon_width / 2;
1756
1757         if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1758                 rgb = '1 1 1';
1759         else
1760                 Accuracy_LoadColors();
1761
1762         float oldposx = pos.x;
1763         vector tmpos = pos;
1764
1765         int column = 0;
1766         FOREACH(Weapons, it != WEP_Null, {
1767                 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1768
1769                 WepSet set = it.m_wepset;
1770                 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1771                         continue;
1772                 if (it.spawnflags & WEP_TYPE_OTHER)
1773                         continue;
1774
1775                 float weapon_alpha;
1776                 if (weapon_stats >= 0)
1777                         weapon_alpha = sbt_fg_alpha;
1778                 else
1779                         weapon_alpha = 0.2 * sbt_fg_alpha;
1780
1781                 // weapon icon
1782                 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1783                 // the accuracy
1784                 if (weapon_stats >= 0) {
1785                         weapons_with_stats += 1;
1786                         average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1787
1788                         string s = sprintf("%d%%", weapon_stats * 100);
1789                         float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1790
1791                         if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1792                                 rgb = Accuracy_GetColor(weapon_stats);
1793
1794                         drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1795                 }
1796                 tmpos.x += weapon_width * rows;
1797                 pos.x += weapon_width * rows;
1798                 if (rows == 2 && column == columns - 1) {
1799                         tmpos.x = oldposx;
1800                         tmpos.y += height;
1801                         pos.y += height;
1802                 }
1803                 ++column;
1804         });
1805
1806         if (weapons_with_stats)
1807                 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1808
1809         panel_size.x += panel_bg_padding * 2; // restore initial width
1810
1811         return end_pos;
1812 }
1813
1814 bool is_item_filtered(entity it)
1815 {
1816         if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1817                 return false;
1818         int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1819         if (mask <= 0)
1820                 return false;
1821         if (it.instanceOfArmor || it.instanceOfHealth)
1822         {
1823                 int ha_mask = floor(mask) % 10;
1824                 switch (ha_mask)
1825                 {
1826                         default: return false;
1827                         case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1828                         case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1829                         case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1830                         case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1831                 }
1832         }
1833         if (it.instanceOfAmmo)
1834         {
1835                 int ammo_mask = floor(mask / 10) % 10;
1836                 return (ammo_mask == 1);
1837         }
1838         return false;
1839 }
1840
1841 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1842 {
1843         scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1844
1845         int disowned_cnt = 0;
1846         int uninteresting_cnt = 0;
1847         IL_EACH(default_order_items, true, {
1848                 int q = g_inventory.inv_items[it.m_id];
1849                 //q = 1; // debug: display all items
1850                 if (is_item_filtered(it))
1851                         ++uninteresting_cnt;
1852                 else if (!q)
1853                         ++disowned_cnt;
1854         });
1855         int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1856         int n = items_cnt - disowned_cnt;
1857         if (n <= 0) return pos;
1858
1859         int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1860         int columns = max(6, ceil(n / rows));
1861
1862         float item_height = hud_fontsize.y * 2.3;
1863         float height = item_height + hud_fontsize.y;
1864
1865         drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1866         pos.y += 1.25 * hud_fontsize.y;
1867         if(panel.current_panel_bg != "0")
1868                 pos.y += panel_bg_border;
1869
1870         panel_pos = pos;
1871         panel_size.y = height * rows;
1872         panel_size.y += panel_bg_padding * 2;
1873
1874         float panel_bg_alpha_save = panel_bg_alpha;
1875         panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1876         HUD_Panel_DrawBg();
1877         panel_bg_alpha = panel_bg_alpha_save;
1878
1879         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1880         if(panel.current_panel_bg != "0")
1881                 end_pos.y += panel_bg_border * 2;
1882
1883         if(panel_bg_padding)
1884         {
1885                 panel_pos += '1 1 0' * panel_bg_padding;
1886                 panel_size -= '2 2 0' * panel_bg_padding;
1887         }
1888
1889         pos = panel_pos;
1890         vector tmp = panel_size;
1891
1892         float item_width = tmp.x / columns / rows;
1893
1894         if (sbt_bg_alpha)
1895                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1896
1897         if(sbt_highlight)
1898         {
1899                 // column highlighting
1900                 for (int i = 0; i < columns; ++i)
1901                         if ((i % 2) == 0)
1902                                 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);
1903
1904                 // row highlighting
1905                 for (int i = 0; i < rows; ++i)
1906                         drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1907         }
1908
1909         if (rows == 2)
1910                 pos.x += item_width / 2;
1911
1912         float oldposx = pos.x;
1913         vector tmpos = pos;
1914
1915         int column = 0;
1916         IL_EACH(default_order_items, !is_item_filtered(it), {
1917                 int n = g_inventory.inv_items[it.m_id];
1918                 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1919                 if (n <= 0) continue;
1920                 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);
1921                 string s = ftos(n);
1922                 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1923                 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1924                 tmpos.x += item_width * rows;
1925                 pos.x += item_width * rows;
1926                 if (rows == 2 && column == columns - 1) {
1927                         tmpos.x = oldposx;
1928                         tmpos.y += height;
1929                         pos.y += height;
1930                 }
1931                 ++column;
1932         });
1933
1934         panel_size.x += panel_bg_padding * 2; // restore initial width
1935
1936         return end_pos;
1937 }
1938
1939 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1940         float px = pos.x;
1941         pos.x += hud_fontsize.x * 0.25;
1942         drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1943         pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1944         drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1945         pos.x = px;
1946         pos.y += hud_fontsize.y;
1947
1948         return pos;
1949 }
1950
1951 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1952         float stat_secrets_found, stat_secrets_total;
1953         float stat_monsters_killed, stat_monsters_total;
1954         float rows = 0;
1955         string val;
1956
1957         // get monster stats
1958         stat_monsters_killed = STAT(MONSTERS_KILLED);
1959         stat_monsters_total = STAT(MONSTERS_TOTAL);
1960
1961         // get secrets stats
1962         stat_secrets_found = STAT(SECRETS_FOUND);
1963         stat_secrets_total = STAT(SECRETS_TOTAL);
1964
1965         // get number of rows
1966         if(stat_secrets_total)
1967                 rows += 1;
1968         if(stat_monsters_total)
1969                 rows += 1;
1970
1971         // if no rows, return
1972         if (!rows)
1973                 return pos;
1974
1975         //  draw table header
1976         drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1977         pos.y += 1.25 * hud_fontsize.y;
1978         if(panel.current_panel_bg != "0")
1979                 pos.y += panel_bg_border;
1980
1981         panel_pos = pos;
1982         panel_size.y = hud_fontsize.y * rows;
1983         panel_size.y += panel_bg_padding * 2;
1984         HUD_Panel_DrawBg();
1985
1986         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1987         if(panel.current_panel_bg != "0")
1988                 end_pos.y += panel_bg_border * 2;
1989
1990         if(panel_bg_padding)
1991         {
1992                 panel_pos += '1 1 0' * panel_bg_padding;
1993                 panel_size -= '2 2 0' * panel_bg_padding;
1994         }
1995
1996         pos = panel_pos;
1997         vector tmp = panel_size;
1998
1999         if (sbt_bg_alpha)
2000                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2001
2002         // draw monsters
2003         if(stat_monsters_total)
2004         {
2005                 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
2006                 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
2007         }
2008
2009         // draw secrets
2010         if(stat_secrets_total)
2011         {
2012                 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
2013                 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
2014         }
2015
2016         panel_size.x += panel_bg_padding * 2; // restore initial width
2017         return end_pos;
2018 }
2019
2020 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
2021 {
2022         int i;
2023         RANKINGS_RECEIVED_CNT = 0;
2024         for (i=RANKINGS_CNT-1; i>=0; --i)
2025                 if (grecordtime[i])
2026                         ++RANKINGS_RECEIVED_CNT;
2027
2028         if (RANKINGS_RECEIVED_CNT == 0)
2029                 return pos;
2030
2031         vector hl_rgb = rgb + '0.5 0.5 0.5';
2032
2033         vector scoreboard_selected_hl_pos = pos;
2034
2035         drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2036         pos.y += 1.25 * hud_fontsize.y;
2037         if(panel.current_panel_bg != "0")
2038                 pos.y += panel_bg_border;
2039
2040         vector scoreboard_selected_hl_size = '0 0 0';
2041         scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
2042         scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
2043
2044         panel_pos = pos;
2045
2046         float namesize = 0;
2047         for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
2048         {
2049                 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
2050                 if(f > namesize)
2051                         namesize = f;
2052         }
2053         bool cut = false;
2054         if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
2055         {
2056                 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2057                 cut = true;
2058         }
2059
2060         float ranksize = 3 * hud_fontsize.x;
2061         float timesize = 5 * hud_fontsize.x;
2062         vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
2063         rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
2064         rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
2065         if (!rankings_cnt)
2066         {
2067                 rankings_cnt = RANKINGS_RECEIVED_CNT;
2068                 rankings_rows = ceil(rankings_cnt / rankings_columns);
2069         }
2070
2071         // expand name column to fill the entire row
2072         float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
2073         namesize += available_space;
2074         columnsize.x += available_space;
2075
2076         panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
2077         panel_size.y += panel_bg_padding * 2;
2078         scoreboard_selected_hl_size.y += panel_size.y;
2079
2080         HUD_Panel_DrawBg();
2081
2082         vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
2083         if(panel.current_panel_bg != "0")
2084                 end_pos.y += panel_bg_border * 2;
2085
2086         if(panel_bg_padding)
2087         {
2088                 panel_pos += '1 1 0' * panel_bg_padding;
2089                 panel_size -= '2 2 0' * panel_bg_padding;
2090         }
2091
2092         pos = panel_pos;
2093
2094         if (sbt_bg_alpha)
2095                 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
2096
2097         vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
2098         string str = "";
2099         int column = 0, j = 0;
2100         string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
2101         int start_item = rankings_start_column * rankings_rows;
2102         for(i = start_item; i < start_item + rankings_cnt; ++i)
2103         {
2104                 int t = grecordtime[i];
2105                 if (t == 0)
2106                         continue;
2107
2108                 if(strdecolorize(grecordholder[i]) == zoned_name_self)
2109                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
2110                 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
2111                         drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2112
2113                 str = count_ordinal(i+1);
2114                 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2115                 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t, true), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
2116                 str = ColorTranslateRGB(grecordholder[i]);
2117                 if(cut)
2118                         str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
2119                 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
2120
2121                 pos.y += 1.25 * hud_fontsize.y;
2122                 j++;
2123                 if(j >= rankings_rows)
2124                 {
2125                         column++;
2126                         j = 0;
2127                         pos.x += panel_size.x / rankings_columns;
2128                         pos.y = panel_pos.y;
2129                 }
2130         }
2131         strfree(zoned_name_self);
2132
2133         if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2134         {
2135                 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2136                 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2137         }
2138
2139         panel_size.x += panel_bg_padding * 2; // restore initial width
2140         return end_pos;
2141 }
2142
2143 bool have_weapon_stats;
2144 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2145 {
2146         if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2147                 return false;
2148         if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2149                 return false;
2150
2151         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2152                 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2153                 && !intermission)
2154         {
2155                 return false;
2156         }
2157
2158         if (!have_weapon_stats)
2159         {
2160                 FOREACH(Weapons, it != WEP_Null, {
2161                         int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2162                         if (weapon_stats >= 0)
2163                         {
2164                                 have_weapon_stats = true;
2165                                 break;
2166                         }
2167                 });
2168                 if (!have_weapon_stats)
2169                         return false;
2170         }
2171
2172         return true;
2173 }
2174
2175 bool have_item_stats;
2176 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2177 {
2178         if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2179                 return false;
2180         if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2181                 return false;
2182
2183         if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2184                 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2185                 && !intermission)
2186         {
2187                 return false;
2188         }
2189
2190         if (!have_item_stats)
2191         {
2192                 IL_EACH(default_order_items, true, {
2193                         if (!is_item_filtered(it))
2194                         {
2195                                 int q = g_inventory.inv_items[it.m_id];
2196                                 //q = 1; // debug: display all items
2197                                 if (q)
2198                                 {
2199                                         have_item_stats = true;
2200                                         break;
2201                                 }
2202                         }
2203                 });
2204                 if (!have_item_stats)
2205                         return false;
2206         }
2207
2208         return true;
2209 }
2210
2211 vector Scoreboard_Spectators_Draw(vector pos) {
2212
2213         entity pl, tm;
2214         string str = "";
2215
2216         for(pl = players.sort_next; pl; pl = pl.sort_next)
2217         {
2218                 if(pl.team == NUM_SPECTATOR)
2219                 {
2220                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2221                                 if(tm.team == NUM_SPECTATOR)
2222                                         break;
2223                         str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2224                         draw_beginBoldFont();
2225                         drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2226                         draw_endBoldFont();
2227                         pos.y += 1.25 * hud_fontsize.y;
2228
2229                         pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2230                         pos.y += 1.25 * hud_fontsize.y;
2231
2232                         break;
2233                 }
2234         }
2235         if (str != "") // if there's at least one spectator
2236                 pos.y += 0.5 * hud_fontsize.y;
2237
2238         return pos;
2239 }
2240
2241 string Scoreboard_Fraglimit_Draw(float limit, bool is_leadlimit)
2242 {
2243         string s_label = (teamplay) ? teamscores_label(ts_primary) : scores_label(ps_primary);
2244         int s_flags = (teamplay) ? teamscores_flags(ts_primary) : scores_flags(ps_primary);
2245         return sprintf((is_leadlimit ? _("^2+%s %s") : _("^5%s %s")), ScoreString(s_flags, limit, 0),
2246                 (s_label == "score") ? CTX(_("SCO^points")) :
2247                 (s_label == "fastest") ? "" : TranslateScoresLabel(s_label));
2248 }
2249
2250 void Scoreboard_Draw()
2251 {
2252         if(!autocvar__hud_configure)
2253         {
2254                 if(!hud_draw_maximized) return;
2255
2256                 // frametime checks allow to toggle the scoreboard even when the game is paused
2257                 if(scoreboard_active) {
2258                         if (scoreboard_fade_alpha == 0)
2259                                 scoreboard_time = time;
2260                         if(hud_configure_menu_open == 1)
2261                                 scoreboard_fade_alpha = 1;
2262                         float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2263                         if (scoreboard_fadeinspeed && frametime)
2264                                 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2265                         else
2266                                 scoreboard_fade_alpha = 1;
2267                         if(hud_fontsize_str != autocvar_hud_fontsize)
2268                         {
2269                                 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2270                                 Scoreboard_initFieldSizes();
2271                                 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2272                         }
2273                 }
2274                 else {
2275                         float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2276                         if (scoreboard_fadeoutspeed && frametime)
2277                                 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2278                         else
2279                                 scoreboard_fade_alpha = 0;
2280                 }
2281
2282                 if (!scoreboard_fade_alpha)
2283                 {
2284                         scoreboard_acc_fade_alpha = 0;
2285                         scoreboard_itemstats_fade_alpha = 0;
2286                         return;
2287                 }
2288         }
2289         else
2290                 scoreboard_fade_alpha = 0;
2291
2292         if (autocvar_hud_panel_scoreboard_dynamichud)
2293                 HUD_Scale_Enable();
2294         else
2295                 HUD_Scale_Disable();
2296
2297         if(scoreboard_fade_alpha <= 0)
2298                 return;
2299         panel_fade_alpha *= scoreboard_fade_alpha;
2300         HUD_Panel_LoadCvars();
2301
2302         sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2303         sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2304         sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2305         sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2306         sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2307         sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2308         sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2309
2310         // don't overlap with con_notify
2311         if(!autocvar__hud_configure)
2312                 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2313
2314         float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2315         float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2316         scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2317         scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2318         panel_pos.x = scoreboard_left;
2319         panel_size.x = fixed_scoreboard_width;
2320
2321         Scoreboard_UpdatePlayerTeams();
2322
2323         scoreboard_top = panel_pos.y;
2324         vector pos = panel_pos;
2325         entity tm;
2326         string str;
2327         vector str_pos;
2328
2329         vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2330
2331         // Begin of Game Info Section
2332         sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2333         sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2334
2335         // Game Info: Game Type
2336         if (scoreboard_ui_enabled == 2)
2337                 str = _("Team Selection");
2338         else
2339                 str = MapInfo_Type_ToText(gametype);
2340         draw_beginBoldFont();
2341         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);
2342         draw_endBoldFont();
2343
2344         pos.y += sb_gameinfo_type_fontsize.y;
2345         // Game Info: Game Detail
2346         if (scoreboard_ui_enabled == 2)
2347         {
2348                 if (scoreboard_selected_team)
2349                         str = sprintf(_("^7Press ^3%s^7 to join the selected team"), translate_key("SPACE"));
2350                 else
2351                         str = sprintf(_("^7Press ^3%s^7 to auto-select a team and join"), translate_key("SPACE"));
2352                 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);
2353
2354                 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3;
2355                 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2356                 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);
2357         }
2358         else
2359         {
2360                 float tl = STAT(TIMELIMIT);
2361                 float fl = STAT(FRAGLIMIT);
2362                 float ll = STAT(LEADLIMIT);
2363                 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2364                 str = "";
2365                 if(tl > 0)
2366                         str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2367                 if(!gametype.m_hidelimits)
2368                 {
2369                         if(fl > 0)
2370                         {
2371                                 if(tl > 0)
2372                                         str = strcat(str, "^7 / "); // delimiter
2373                                 str = strcat(str, Scoreboard_Fraglimit_Draw(fl, false));
2374                         }
2375                         if(ll > 0)
2376                         {
2377                                 if(tl > 0 || fl > 0)
2378                                 {
2379                                         // delimiter
2380                                         if (ll_and_fl && fl > 0)
2381                                                 str = strcat(str, "^7 & ");
2382                                         else
2383                                                 str = strcat(str, "^7 / ");
2384                                 }
2385                                 str = strcat(str, Scoreboard_Fraglimit_Draw(ll, true));
2386                         }
2387                 }
2388                 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
2389                 // map name and player count
2390                 if (campaign)
2391                         str = "";
2392                 else
2393                         str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
2394                 str = strcat("^7", _("Map:"), " ^2", mi_shortname, "    ", str); // reusing "Map:" translatable string
2395                 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2396         }
2397         // End of Game Info Section
2398
2399         pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2400         if(panel.current_panel_bg != "0")
2401                 pos.y += panel_bg_border;
2402
2403         // Draw the scoreboard
2404         float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2405         if(scale <= 0)
2406                 scale = 0.25;
2407         vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2408
2409         if(teamplay)
2410         {
2411                 vector panel_bg_color_save = panel_bg_color;
2412                 vector team_score_baseoffset;
2413                 vector team_size_baseoffset;
2414                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2415                 {
2416                         // put team score to the left of scoreboard (and team size to the right)
2417                         team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2418                         team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2419                         if(panel.current_panel_bg != "0")
2420                         {
2421                                 team_score_baseoffset.x -= panel_bg_border;
2422                                 team_size_baseoffset.x += panel_bg_border;
2423                         }
2424                 }
2425                 else
2426                 {
2427                         // put team score to the right of scoreboard (and team size to the left)
2428                         team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2429                         team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2430                         if(panel.current_panel_bg != "0")
2431                         {
2432                                 team_score_baseoffset.x += panel_bg_border;
2433                                 team_size_baseoffset.x -= panel_bg_border;
2434                         }
2435                 }
2436
2437                 int team_size_total = 0;
2438                 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2439                 {
2440                         // calculate team size total (sum of all team sizes)
2441                         for(tm = teams.sort_next; tm; tm = tm.sort_next)
2442                                 if(tm.team != NUM_SPECTATOR)
2443                                         team_size_total += tm.team_size;
2444                 }
2445
2446                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2447                 {
2448                         if(tm.team == NUM_SPECTATOR)
2449                                 continue;
2450                         if(!tm.team)
2451                                 continue;
2452
2453                         draw_beginBoldFont();
2454                         vector rgb = Team_ColorRGB(tm.team);
2455                         str = ftos(tm.(teamscores(ts_primary)));
2456                         if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2457                         {
2458                                 // team score on the left (default)
2459                                 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2460                         }
2461                         else
2462                         {
2463                                 // team score on the right
2464                                 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2465                         }
2466                         drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2467
2468                         // team size (if set to show on the side)
2469                         if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2470                         {
2471                                 // calculate the starting position for the whole team size info string
2472                                 str = sprintf("%d/%d", tm.team_size, team_size_total);
2473                                 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2474                                 {
2475                                         // team size on the left
2476                                         str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2477                                 }
2478                                 else
2479                                 {
2480                                         // team size on the right
2481                                         str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2482                                 }
2483                                 str = sprintf("%d", tm.team_size);
2484                                 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2485                                 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2486                                 str = sprintf("/%d", team_size_total);
2487                                 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2488                         }
2489
2490
2491                         // secondary score, e.g. keyhunt
2492                         if(ts_primary != ts_secondary)
2493                         {
2494                                 str = ftos(tm.(teamscores(ts_secondary)));
2495                                 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2496                                 {
2497                                         // left
2498                                         str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2499                                 }
2500                                 else
2501                                 {
2502                                         // right
2503                                         str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2504                                 }
2505
2506                                 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2507                         }
2508                         draw_endBoldFont();
2509                         if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2510                                 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2511                         else if(panel_bg_color_team > 0)
2512                                 panel_bg_color = rgb * panel_bg_color_team;
2513                         else
2514                                 panel_bg_color = rgb;
2515                         pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2516                 }
2517                 panel_bg_color = panel_bg_color_save;
2518         }
2519         else
2520         {
2521                 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2522                         if(tm.team != NUM_SPECTATOR)
2523                                 break;
2524
2525                 // display it anyway
2526                 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2527         }
2528
2529         // draw scoreboard spectators before accuracy and item stats
2530         if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2531                 pos = Scoreboard_Spectators_Draw(pos);
2532         }
2533
2534         // draw accuracy and item stats
2535         if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2536                 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2537         if (Scoreboard_ItemStats_WouldDraw(pos.y))
2538                 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2539
2540         // draw scoreboard spectators after accuracy and item stats and before rankings
2541         if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2542                 pos = Scoreboard_Spectators_Draw(pos);
2543         }
2544
2545         if(MUTATOR_CALLHOOK(ShowRankings)) {
2546                 string ranktitle = M_ARGV(0, string);
2547                 string unit = GetSpeedUnit(autocvar_hud_speed_unit);
2548                 float conversion_factor = GetSpeedUnitFactor(autocvar_hud_speed_unit);
2549                 if(race_speedaward_alltimebest)
2550                 {
2551                         string name;
2552                         float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2553                         str = "";
2554                         if(race_speedaward)
2555                         {
2556                                 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2557                                 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward * conversion_factor, unit, name);
2558                                 str = strcat(str, " / ");
2559                         }
2560                         name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2561                         str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest * conversion_factor, unit, name));
2562                         drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2563                         pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2564                 }
2565                 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2566         }
2567         else
2568                 rankings_cnt = 0;
2569
2570         // draw scoreboard spectators after rankings
2571         if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2572                 pos = Scoreboard_Spectators_Draw(pos);
2573         }
2574
2575         pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2576
2577         // draw scoreboard spectators after mapstats
2578         if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2579                 pos = Scoreboard_Spectators_Draw(pos);
2580         }
2581
2582
2583         // print information about respawn status
2584         float respawn_time = STAT(RESPAWN_TIME);
2585         if(!intermission && respawn_time)
2586         {
2587                 if(respawn_time < 0)
2588                 {
2589                         // a negative number means we are awaiting respawn, time value is still the same
2590                         respawn_time *= -1; // remove mark now that we checked it
2591
2592                         if(respawn_time < time) // it happens for a few frames when server is respawning the player
2593                                 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2594                         else
2595                                 str = sprintf(_("^1Respawning in ^3%s^1..."),
2596                                         (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2597                                                 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2598                                                 :
2599                                                 count_seconds(ceil(respawn_time - time))
2600                                         )
2601                                 );
2602                 }
2603                 else if(time < respawn_time)
2604                 {
2605                         str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2606                                 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2607                                         count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2608                                         :
2609                                         count_seconds(ceil(respawn_time - time))
2610                                 )
2611                         );
2612                 }
2613                 else if(time >= respawn_time)
2614                         str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2615
2616                 pos.y += 1.2 * hud_fontsize.y;
2617                 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2618         }
2619
2620         pos.y += hud_fontsize.y;
2621         if (scoreboard_fade_alpha < 1)
2622                 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2623         else if (pos.y != scoreboard_bottom)
2624         {
2625                 if (pos.y > scoreboard_bottom)
2626                         scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2627                 else
2628                         scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2629         }
2630
2631         if (rankings_cnt)
2632         {
2633                 if (scoreboard_fade_alpha == 1)
2634                 {
2635                         if (scoreboard_bottom > 0.95 * vid_conheight)
2636                                 rankings_rows = max(1, rankings_rows - 1);
2637                         else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2638                                 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2639                 }
2640                 rankings_cnt = rankings_rows * rankings_columns;
2641         }
2642 }