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