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