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