1 #include "scoreboard.qh"
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>
21 void Scoreboard_Draw_Export(int fh)
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");
41 const int MAX_SBT_FIELDS = MAX_SCORE;
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];
48 string autocvar_hud_fontsize;
49 string hud_fontsize_str;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
58 float sbt_highlight_alpha_eliminated;
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 = "";
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;
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;
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;
100 bool autocvar_hud_panel_scoreboard_dynamichud = false;
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 = " ";
111 float scoreboard_time;
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)
118 label = "bckills"; // first case in the switch
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;
169 void HUD_Scoreboard_UI_Disable()
171 scoreboard_showscores = false;
172 scoreboard_ui_enabled = 0;
173 scoreboard_selected_panel = 0;
174 scoreboard_selected_player = NULL;
175 scoreboard_selected_team = NULL;
176 scoreboard_ui_disabled_time = time;
179 // mode: 0 normal, 1 team selection
180 void Scoreboard_UI_Enable(int mode)
184 if (scoreboard_ui_enabled == 2 || !teamplay)
186 // Do not allow reopening the team selection for some time after it has been closed.
187 // It works around a bug caused by the server sending scoreboard_team_selection every frame
188 // until client selects a team and joins: scoreboard_team_selection gets executed even
189 // after client already joined
190 // For the record, with the menu dialog this workaround is not needed because it takes
191 // some time to fade away
192 if (time < scoreboard_ui_disabled_time + 0.5)
194 scoreboard_ui_enabled = 2;
195 scoreboard_selected_panel = SB_PANEL_SCOREBOARD;
199 if (scoreboard_ui_enabled == 1)
201 scoreboard_ui_enabled = 1;
202 scoreboard_selected_panel = SB_PANEL_FIRST;
204 scoreboard_selected_player = NULL;
205 scoreboard_selected_team = NULL;
206 scoreboard_selected_panel_time = time;
209 int rankings_start_column;
210 int rankings_rows = 0;
211 int rankings_columns = 0;
212 int rankings_cnt = 0;
213 float HUD_Scoreboard_InputEvent(float bInputType, float nPrimary, float nSecondary)
217 if(!scoreboard_ui_enabled)
222 mousepos.x = nPrimary;
223 mousepos.y = nSecondary;
230 // at this point bInputType can only be 0 or 1 (key pressed or released)
231 bool key_pressed = (bInputType == 0);
233 // ESC to exit (TAB-ESC works too)
234 if(nPrimary == K_ESCAPE)
238 HUD_Scoreboard_UI_Disable();
242 // block any input while a menu dialog is fading
243 if(autocvar__menu_alpha)
249 // allow console bind to work
250 string con_keys = findkeysforcommand("toggleconsole", 0);
251 int keys = tokenize(con_keys); // findkeysforcommand returns data for this
253 bool hit_con_bind = false;
255 for (i = 0; i < keys; ++i)
257 if(nPrimary == stof(argv(i)))
262 if(nPrimary == K_ALT) hudShiftState |= S_ALT;
263 if(nPrimary == K_CTRL) hudShiftState |= S_CTRL;
264 if(nPrimary == K_SHIFT) hudShiftState |= S_SHIFT;
265 if(nPrimary == K_TAB) hudShiftState |= S_TAB;
268 if(nPrimary == K_ALT) hudShiftState -= (hudShiftState & S_ALT);
269 if(nPrimary == K_CTRL) hudShiftState -= (hudShiftState & S_CTRL);
270 if(nPrimary == K_SHIFT) hudShiftState -= (hudShiftState & S_SHIFT);
271 if(nPrimary == K_TAB) hudShiftState -= (hudShiftState & S_TAB);
274 if(nPrimary == K_TAB)
278 if (scoreboard_ui_enabled == 2)
279 goto downarrow_action;
281 ++scoreboard_selected_panel;
282 if (scoreboard_selected_panel == SB_PANEL_RANKINGS && !rankings_cnt)
283 ++scoreboard_selected_panel;
284 if (scoreboard_selected_panel >= SB_PANEL_MAX)
285 scoreboard_selected_panel = 1;
287 scoreboard_selected_panel_time = time;
289 else if(nPrimary == K_DOWNARROW)
293 LABEL(downarrow_action);
294 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
296 if (scoreboard_ui_enabled == 2)
298 entity curr_team = NULL;
299 bool scoreboard_selected_team_found = false;
300 if (!scoreboard_selected_team)
301 scoreboard_selected_team_found = true;
303 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
305 if(tm.team == NUM_SPECTATOR)
308 if (scoreboard_selected_team_found)
310 if (scoreboard_selected_team == tm)
311 scoreboard_selected_team_found = true;
314 if (curr_team == scoreboard_selected_team) // loop reached the last team
316 scoreboard_selected_team = curr_team;
321 entity curr_pl = NULL;
322 bool scoreboard_selected_player_found = false;
323 if (!scoreboard_selected_player)
324 scoreboard_selected_player_found = true;
326 for(tm = teams.sort_next; tm; tm = tm.sort_next)
328 if(tm.team != NUM_SPECTATOR)
329 for(pl = players.sort_next; pl; pl = pl.sort_next)
331 if(pl.team != tm.team)
334 if (scoreboard_selected_player_found)
336 if (scoreboard_selected_player == pl)
337 scoreboard_selected_player_found = true;
341 if (curr_pl == scoreboard_selected_player) // loop reached the last player
343 scoreboard_selected_player = curr_pl;
347 else if(nPrimary == K_UPARROW)
351 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
353 if (scoreboard_ui_enabled == 2)
355 entity prev_team = NULL;
356 for(entity tm = teams.sort_next; tm; tm = tm.sort_next)
358 if(tm.team == NUM_SPECTATOR)
360 if (tm == scoreboard_selected_team)
365 scoreboard_selected_team = prev_team;
369 entity prev_pl = NULL;
371 for(tm = teams.sort_next; tm; tm = tm.sort_next)
373 if(tm.team != NUM_SPECTATOR)
374 for(pl = players.sort_next; pl; pl = pl.sort_next)
376 if(pl.team != tm.team)
378 if (pl == scoreboard_selected_player)
384 scoreboard_selected_player = prev_pl;
388 else if(nPrimary == K_RIGHTARROW)
392 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
393 rankings_start_column = min(rankings_start_column + 1, (ceil(RANKINGS_RECEIVED_CNT / rankings_rows) - rankings_columns));
395 else if(nPrimary == K_LEFTARROW)
399 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
400 rankings_start_column = max(rankings_start_column - 1, 0);
402 else if(nPrimary == K_ENTER || nPrimary == K_SPACE || nPrimary == K_KP_ENTER)
406 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
408 if (scoreboard_ui_enabled == 2)
411 if (!scoreboard_selected_team || (hudShiftState & S_SHIFT))
414 team_name = Static_Team_ColorName(scoreboard_selected_team.team);
415 localcmd(sprintf("cmd selectteam %s; cmd join", team_name));
416 HUD_Scoreboard_UI_Disable();
418 else if (!scoreboard_selected_player || (hudShiftState & S_SHIFT))
421 HUD_Scoreboard_UI_Disable();
424 localcmd(sprintf("spectate %d", scoreboard_selected_player.sv_entnum + 1));
427 else if(nPrimary == 't' && (hudShiftState & S_CTRL))
431 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
433 if (scoreboard_selected_player)
435 localcmd(sprintf("commandmode tell \"%s^7\"", entcs_GetName(scoreboard_selected_player.sv_entnum)));
436 HUD_Scoreboard_UI_Disable();
440 else if(nPrimary == 'k' && (hudShiftState & S_CTRL))
444 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
446 if (scoreboard_selected_player)
447 localcmd(sprintf("vcall kick \"%s^7\"", entcs_GetName(scoreboard_selected_player.sv_entnum)));
450 else if(hit_con_bind || nPrimary == K_PAUSE)
456 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
457 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
459 #define SB_EXTRA_SORTING_FIELDS 5
460 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
461 void Scoreboard_InitScores()
465 ps_primary = ps_secondary = NULL;
466 ts_primary = ts_secondary = -1;
467 FOREACH(Scores, true, {
468 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
469 if(f == SFL_SORT_PRIO_PRIMARY)
471 if(f == SFL_SORT_PRIO_SECONDARY)
473 if(ps_primary == it || ps_secondary == it)
475 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
476 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
477 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
478 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
479 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
481 if(ps_secondary == NULL)
482 ps_secondary = ps_primary;
484 for(i = 0; i < MAX_TEAMSCORE; ++i)
486 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
487 if(f == SFL_SORT_PRIO_PRIMARY)
489 if(f == SFL_SORT_PRIO_SECONDARY)
492 if(ts_secondary == -1)
493 ts_secondary = ts_primary;
495 Cmd_Scoreboard_SetFields(0);
499 void Scoreboard_UpdatePlayerTeams()
501 static float update_time;
502 if (time <= update_time)
508 for(pl = players.sort_next; pl; pl = pl.sort_next)
511 int Team = entcs_GetScoreTeam(pl.sv_entnum);
512 if(SetTeam(pl, Team))
515 Scoreboard_UpdatePlayerPos(pl);
519 pl = players.sort_next;
524 print(strcat("PNUM: ", ftos(num), "\n"));
529 int Scoreboard_CompareScore(int vl, int vr, int f)
531 TC(int, vl); TC(int, vr); TC(int, f);
532 if(f & SFL_ZERO_IS_WORST)
534 if(vl == 0 && vr != 0)
536 if(vl != 0 && vr == 0)
540 return IS_INCREASING(f);
542 return IS_DECREASING(f);
546 float Scoreboard_ComparePlayerScores(entity left, entity right)
548 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
549 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
556 if(vl == NUM_SPECTATOR)
558 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
560 if(!left.gotscores && right.gotscores)
567 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
571 if (!fld) fld = ps_primary;
572 else if (ps_secondary == ps_primary) continue;
573 else fld = ps_secondary;
577 fld = sb_extra_sorting_field[i];
578 if (fld == ps_primary || fld == ps_secondary) continue;
582 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
583 if (r >= 0) return r;
586 if (left.sv_entnum < right.sv_entnum)
592 void Scoreboard_UpdatePlayerPos(entity player)
595 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
597 SORT_SWAP(player, ent);
599 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
601 SORT_SWAP(ent, player);
605 float Scoreboard_CompareTeamScores(entity left, entity right)
607 if(left.team == NUM_SPECTATOR)
609 if(right.team == NUM_SPECTATOR)
614 for(int i = -2; i < MAX_TEAMSCORE; ++i)
618 if (fld_idx == -1) fld_idx = ts_primary;
619 else if (ts_secondary == ts_primary) continue;
620 else fld_idx = ts_secondary;
625 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
628 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
629 if (r >= 0) return r;
632 if (left.team < right.team)
638 void Scoreboard_UpdateTeamPos(entity Team)
641 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
643 SORT_SWAP(Team, ent);
645 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
647 SORT_SWAP(ent, Team);
651 void Cmd_Scoreboard_Help()
653 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
654 LOG_HELP(_("Usage:"));
655 LOG_HELP("^2scoreboard_columns_set ^3default");
656 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
657 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
658 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
659 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
660 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
661 LOG_HELP(_("The following field names are recognized (case insensitive):"));
667 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
668 "of game types, then a slash, to make the field show up only in these\n"
669 "or in all but these game types. You can also specify 'all' as a\n"
670 "field to show all fields available for the current game mode."));
673 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
674 "include/exclude ALL teams/noteams game modes."));
677 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
678 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
679 "right of the vertical bar aligned to the right."));
680 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
681 "other gamemodes except DM."));
684 // NOTE: adding a gametype with ? to not warn for an optional field
685 // make sure it's excluded in a previous exclusive rule, if any
686 // otherwise the previous exclusive rule warns anyway
687 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
688 #define SCOREBOARD_DEFAULT_COLUMNS \
689 "ping pl fps name |" \
690 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
691 " -teams,lms/deaths +ft,tdm/deaths" \
693 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
694 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
695 " +tdm,ft,dom,ons,as/teamkills"\
696 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
697 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
698 " +lms/lives +lms/rank" \
699 " +kh/kckills +kh/losses +kh/caps" \
700 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
701 " +as/objectives +nb/faults +nb/goals" \
702 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
703 " +dom/ticks +dom/takes" \
704 " -lms,rc,cts,inv,nb/score"
706 void Cmd_Scoreboard_SetFields(int argc)
711 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
715 return; // do nothing, we don't know gametype and scores yet
717 // sbt_fields uses strunzone on the titles!
718 if(!sbt_field_title[0])
719 for(i = 0; i < MAX_SBT_FIELDS; ++i)
720 sbt_field_title[i] = strzone("(null)");
722 // TODO: re enable with gametype dependant cvars?
723 if(argc < 3) // no arguments provided
724 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
727 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
731 if(argv(2) == "default" || argv(2) == "expand_default")
733 if(argv(2) == "expand_default")
734 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
735 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
737 else if(argv(2) == "all" || argv(2) == "ALL")
739 string s = "ping pl name |"; // scores without label (not really scores)
742 // scores without label
743 s = strcat(s, " ", "sum");
744 s = strcat(s, " ", "kdratio");
745 s = strcat(s, " ", "frags");
747 FOREACH(Scores, true, {
749 if(it != ps_secondary)
750 if(scores_label(it) != "")
751 s = strcat(s, " ", scores_label(it));
753 if(ps_secondary != ps_primary)
754 s = strcat(s, " ", scores_label(ps_secondary));
755 s = strcat(s, " ", scores_label(ps_primary));
756 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
763 hud_fontsize = HUD_GetFontsize("hud_fontsize");
765 for(i = 1; i < argc - 1; ++i)
768 bool nocomplain = false;
769 if(substring(str, 0, 1) == "?")
772 str = substring(str, 1, strlen(str) - 1);
775 slash = strstrofs(str, "/", 0);
778 pattern = substring(str, 0, slash);
779 str = substring(str, slash + 1, strlen(str) - (slash + 1));
781 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
785 str = strtolower(str);
786 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
787 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
792 // fields without a label (not networked via the score system)
793 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
794 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
795 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
796 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
797 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
798 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
799 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
800 default: // fields with a label
802 // map alternative labels
803 if (str == "damage") str = "dmg";
804 if (str == "damagetaken") str = "dmgtaken";
806 FOREACH(Scores, true, {
807 if (str == strtolower(scores_label(it))) {
809 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
813 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
814 if(!nocomplain && str != "fps") // server can disable the fps field
815 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
817 strfree(sbt_field_title[sbt_num_fields]);
818 sbt_field_size[sbt_num_fields] = 0;
822 sbt_field[sbt_num_fields] = j;
825 if(j == ps_secondary)
826 have_secondary = true;
831 if(sbt_num_fields >= MAX_SBT_FIELDS)
835 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
837 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
838 have_secondary = true;
839 if(ps_primary == ps_secondary)
840 have_secondary = true;
841 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
843 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
847 strfree(sbt_field_title[sbt_num_fields]);
848 for(i = sbt_num_fields; i > 0; --i)
850 sbt_field_title[i] = sbt_field_title[i-1];
851 sbt_field_size[i] = sbt_field_size[i-1];
852 sbt_field[i] = sbt_field[i-1];
854 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
855 sbt_field[0] = SP_NAME;
857 LOG_INFO("fixed missing field 'name'");
861 strfree(sbt_field_title[sbt_num_fields]);
862 for(i = sbt_num_fields; i > 1; --i)
864 sbt_field_title[i] = sbt_field_title[i-1];
865 sbt_field_size[i] = sbt_field_size[i-1];
866 sbt_field[i] = sbt_field[i-1];
868 sbt_field_title[1] = strzone("|");
869 sbt_field[1] = SP_SEPARATOR;
870 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
872 LOG_INFO("fixed missing field '|'");
875 else if(!have_separator)
877 strcpy(sbt_field_title[sbt_num_fields], "|");
878 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
879 sbt_field[sbt_num_fields] = SP_SEPARATOR;
881 LOG_INFO("fixed missing field '|'");
885 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
886 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
887 sbt_field[sbt_num_fields] = ps_secondary;
889 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
893 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
894 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
895 sbt_field[sbt_num_fields] = ps_primary;
897 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
901 sbt_field[sbt_num_fields] = SP_END;
904 string Scoreboard_AddPlayerId(string pl_name, entity pl)
906 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
907 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
908 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
912 vector sbt_field_rgb;
913 string sbt_field_icon0;
914 string sbt_field_icon1;
915 string sbt_field_icon2;
916 vector sbt_field_icon0_rgb;
917 vector sbt_field_icon1_rgb;
918 vector sbt_field_icon2_rgb;
919 string Scoreboard_GetName(entity pl)
921 if(ready_waiting && pl.ready)
923 sbt_field_icon0 = "gfx/scoreboard/player_ready";
927 int f = entcs_GetClientColors(pl.sv_entnum);
929 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
930 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
931 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
932 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
933 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
936 return entcs_GetName(pl.sv_entnum);
939 string Scoreboard_GetField(entity pl, PlayerScoreField field)
941 float tmp, num, denom;
944 sbt_field_rgb = '1 1 1';
945 sbt_field_icon0 = "";
946 sbt_field_icon1 = "";
947 sbt_field_icon2 = "";
948 sbt_field_icon0_rgb = '1 1 1';
949 sbt_field_icon1_rgb = '1 1 1';
950 sbt_field_icon2_rgb = '1 1 1';
955 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
956 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
960 tmp = max(0, min(220, f-80)) / 220;
961 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
967 f = pl.ping_packetloss;
968 tmp = pl.ping_movementloss;
969 if(f == 0 && tmp == 0)
971 str = ftos(ceil(f * 100));
973 str = strcat(str, "~", ftos(ceil(tmp * 100)));
974 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
975 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
979 str = Scoreboard_GetName(pl);
980 if (autocvar_hud_panel_scoreboard_playerid)
981 str = Scoreboard_AddPlayerId(str, pl);
985 f = pl.(scores(SP_KILLS));
986 f -= pl.(scores(SP_SUICIDES));
990 num = pl.(scores(SP_KILLS));
991 denom = pl.(scores(SP_DEATHS));
994 sbt_field_rgb = '0 1 0';
995 str = sprintf("%d", num);
996 } else if(num <= 0) {
997 sbt_field_rgb = '1 0 0';
998 str = sprintf("%.1f", num/denom);
1000 str = sprintf("%.1f", num/denom);
1004 f = pl.(scores(SP_KILLS));
1005 f -= pl.(scores(SP_DEATHS));
1008 sbt_field_rgb = '0 1 0';
1010 sbt_field_rgb = '1 1 1';
1012 sbt_field_rgb = '1 0 0';
1018 float elo = pl.(scores(SP_ELO));
1020 case -1: return "...";
1021 case -2: return _("N/A");
1022 default: return ftos(elo);
1028 float fps = pl.(scores(SP_FPS));
1031 sbt_field_rgb = '1 1 1';
1032 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
1034 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
1035 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
1039 case SP_DMG: case SP_DMGTAKEN:
1040 return sprintf("%.1f k", pl.(scores(field)) / 1000);
1042 default: case SP_SCORE:
1043 tmp = pl.(scores(field));
1044 f = scores_flags(field);
1045 if(field == ps_primary)
1046 sbt_field_rgb = '1 1 0';
1047 else if(field == ps_secondary)
1048 sbt_field_rgb = '0 1 1';
1050 sbt_field_rgb = '1 1 1';
1051 return ScoreString(f, tmp);
1056 float sbt_fixcolumnwidth_len;
1057 float sbt_fixcolumnwidth_iconlen;
1058 float sbt_fixcolumnwidth_marginlen;
1060 string Scoreboard_FixColumnWidth(int i, string str)
1066 sbt_fixcolumnwidth_iconlen = 0;
1068 if(sbt_field_icon0 != "")
1070 sz = draw_getimagesize(sbt_field_icon0);
1072 if(sbt_fixcolumnwidth_iconlen < f)
1073 sbt_fixcolumnwidth_iconlen = f;
1076 if(sbt_field_icon1 != "")
1078 sz = draw_getimagesize(sbt_field_icon1);
1080 if(sbt_fixcolumnwidth_iconlen < f)
1081 sbt_fixcolumnwidth_iconlen = f;
1084 if(sbt_field_icon2 != "")
1086 sz = draw_getimagesize(sbt_field_icon2);
1088 if(sbt_fixcolumnwidth_iconlen < f)
1089 sbt_fixcolumnwidth_iconlen = f;
1092 if(sbt_fixcolumnwidth_iconlen != 0)
1094 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
1095 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
1098 sbt_fixcolumnwidth_marginlen = 0;
1100 if(sbt_field[i] == SP_NAME) // name gets all remaining space
1103 float remaining_space = 0;
1104 for(j = 0; j < sbt_num_fields; ++j)
1106 if (sbt_field[i] != SP_SEPARATOR)
1107 remaining_space += sbt_field_size[j] + hud_fontsize.x;
1108 sbt_field_size[i] = panel_size.x - remaining_space;
1110 if (sbt_fixcolumnwidth_iconlen != 0)
1111 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1112 float namesize = panel_size.x - remaining_space;
1113 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1114 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
1116 max_namesize = vid_conwidth - remaining_space;
1119 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
1121 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
1122 if(sbt_field_size[i] < f)
1123 sbt_field_size[i] = f;
1128 void Scoreboard_initFieldSizes()
1130 for(int i = 0; i < sbt_num_fields; ++i)
1132 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
1133 Scoreboard_FixColumnWidth(i, "");
1137 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
1140 vector column_dim = eY * panel_size.y;
1142 column_dim.y -= 1.25 * hud_fontsize.y;
1143 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
1144 pos.x += hud_fontsize.x * 0.5;
1145 for(i = 0; i < sbt_num_fields; ++i)
1147 if(sbt_field[i] == SP_SEPARATOR)
1149 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1152 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1153 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1154 pos.x += column_dim.x;
1156 if(sbt_field[i] == SP_SEPARATOR)
1158 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1159 for(i = sbt_num_fields - 1; i > 0; --i)
1161 if(sbt_field[i] == SP_SEPARATOR)
1164 pos.x -= sbt_field_size[i];
1169 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
1170 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1173 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
1174 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
1175 pos.x -= hud_fontsize.x;
1179 pos.x = panel_pos.x;
1180 pos.y += 1.25 * hud_fontsize.y;
1184 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
1186 TC(bool, is_self); TC(int, pl_number);
1188 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
1190 vector h_pos = item_pos;
1191 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1192 // alternated rows highlighting
1193 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1195 if (pl == scoreboard_selected_player)
1196 drawfill(h_pos, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1199 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1200 else if((sbt_highlight) && (!(pl_number % 2)))
1201 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1203 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
1205 vector pos = item_pos;
1206 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
1208 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1210 pos.x += hud_fontsize.x * 0.5;
1211 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1212 vector tmp = '0 0 0';
1214 PlayerScoreField field;
1215 for(i = 0; i < sbt_num_fields; ++i)
1217 field = sbt_field[i];
1218 if(field == SP_SEPARATOR)
1221 if(is_spec && field != SP_NAME && field != SP_PING) {
1222 pos.x += sbt_field_size[i] + hud_fontsize.x;
1225 str = Scoreboard_GetField(pl, field);
1226 str = Scoreboard_FixColumnWidth(i, str);
1228 pos.x += sbt_field_size[i] + hud_fontsize.x;
1230 if(field == SP_NAME) {
1231 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
1232 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1234 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
1235 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1238 tmp.x = sbt_field_size[i] + hud_fontsize.x;
1239 if(sbt_field_icon0 != "")
1240 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1241 if(sbt_field_icon1 != "")
1242 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1243 if(sbt_field_icon2 != "")
1244 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1247 if(sbt_field[i] == SP_SEPARATOR)
1249 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
1250 for(i = sbt_num_fields-1; i > 0; --i)
1252 field = sbt_field[i];
1253 if(field == SP_SEPARATOR)
1256 if(is_spec && field != SP_NAME && field != SP_PING) {
1257 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1261 str = Scoreboard_GetField(pl, field);
1262 str = Scoreboard_FixColumnWidth(i, str);
1264 if(field == SP_NAME) {
1265 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
1266 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
1268 tmp.x = sbt_fixcolumnwidth_len;
1269 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
1272 tmp.x = sbt_field_size[i];
1273 if(sbt_field_icon0 != "")
1274 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1275 if(sbt_field_icon1 != "")
1276 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
1277 if(sbt_field_icon2 != "")
1278 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
1279 pos.x -= sbt_field_size[i] + hud_fontsize.x;
1284 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1287 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
1290 vector h_pos = item_pos;
1291 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1293 bool complete = (this_team == NUM_SPECTATOR);
1296 if((sbt_highlight) && (!(pl_number % 2)))
1297 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1299 vector pos = item_pos;
1300 pos.x += hud_fontsize.x * 0.5;
1301 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1303 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1305 width_limit -= stringwidth("...", false, hud_fontsize);
1306 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1307 static float max_name_width = 0;
1309 float fieldsize = 0;
1310 float min_fieldsize = 0;
1311 float fieldpadding = hud_fontsize.x * 0.25;
1312 if(this_team == NUM_SPECTATOR)
1314 if(autocvar_hud_panel_scoreboard_spectators_showping)
1315 min_fieldsize = stringwidth("999", false, hud_fontsize);
1317 else if(autocvar_hud_panel_scoreboard_others_showscore)
1318 min_fieldsize = stringwidth("99", false, hud_fontsize);
1319 for(i = 0; pl; pl = pl.sort_next)
1321 if(pl.team != this_team)
1323 if(pl == ignored_pl)
1327 if(this_team == NUM_SPECTATOR)
1329 if(autocvar_hud_panel_scoreboard_spectators_showping)
1330 field = Scoreboard_GetField(pl, SP_PING);
1332 else if(autocvar_hud_panel_scoreboard_others_showscore)
1333 field = Scoreboard_GetField(pl, SP_SCORE);
1335 string str = entcs_GetName(pl.sv_entnum);
1336 if (autocvar_hud_panel_scoreboard_playerid)
1337 str = Scoreboard_AddPlayerId(str, pl);
1338 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1339 float column_width = stringwidth(str, true, hud_fontsize);
1340 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1342 if(column_width > max_name_width)
1343 max_name_width = column_width;
1344 column_width = max_name_width;
1348 fieldsize = stringwidth(field, false, hud_fontsize);
1349 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1352 if(pos.x + column_width > width_limit)
1357 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1362 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1363 pos.y += hud_fontsize.y * 1.25;
1367 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD && scoreboard_ui_enabled == 1)
1369 if (pl == scoreboard_selected_player)
1371 h_size.x = column_width + hud_fontsize.x * 0.25;
1372 h_size.y = hud_fontsize.y;
1373 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, rgb, 0.44 * panel_fg_alpha, DRAWFLAG_NORMAL);
1377 vector name_pos = pos;
1378 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1379 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1380 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1383 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1384 h_size.y = hud_fontsize.y;
1385 vector field_pos = pos;
1386 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1387 field_pos.x += column_width - h_size.x;
1389 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1390 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1391 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1395 h_size.x = column_width + hud_fontsize.x * 0.25;
1396 h_size.y = hud_fontsize.y;
1397 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1399 pos.x += column_width;
1400 pos.x += hud_fontsize.x;
1402 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1405 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1407 int max_players = 999;
1408 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1410 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1413 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1414 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1415 height /= team_count;
1418 height -= panel_bg_padding * 2; // - padding
1419 max_players = floor(height / (hud_fontsize.y * 1.25));
1420 if(max_players <= 1)
1422 if(max_players == tm.team_size)
1427 entity me = playerslots[current_player];
1429 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1430 panel_size.y += panel_bg_padding * 2;
1432 vector scoreboard_selected_hl_pos = pos;
1433 vector scoreboard_selected_hl_size = '0 0 0';
1434 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1435 scoreboard_selected_hl_size.y = panel_size.y;
1439 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1440 if(panel.current_panel_bg != "0")
1441 end_pos.y += panel_bg_border * 2;
1443 if(panel_bg_padding)
1445 panel_pos += '1 1 0' * panel_bg_padding;
1446 panel_size -= '2 2 0' * panel_bg_padding;
1450 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1454 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1456 pos.y += 1.25 * hud_fontsize.y;
1459 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1461 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1464 // print header row and highlight columns
1465 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1467 // fill the table and draw the rows
1468 bool is_self = false;
1469 bool self_shown = false;
1471 for(pl = players.sort_next; pl; pl = pl.sort_next)
1473 if(pl.team != tm.team)
1475 if(i == max_players - 2 && pl != me)
1477 if(!self_shown && me.team == tm.team)
1479 Scoreboard_DrawItem(pos, rgb, me, true, i);
1481 pos.y += 1.25 * hud_fontsize.y;
1485 if(i >= max_players - 1)
1487 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1490 is_self = (pl.sv_entnum == current_player);
1491 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1494 pos.y += 1.25 * hud_fontsize.y;
1498 if (scoreboard_selected_panel == SB_PANEL_SCOREBOARD)
1500 if (scoreboard_ui_enabled == 1 || (tm && scoreboard_selected_team == tm))
1502 float _alpha = (scoreboard_ui_enabled == 2) ? 0.2 : 0.3 * max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
1503 _alpha *= panel_fg_alpha;
1505 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', _alpha, DRAWFLAG_NORMAL);
1509 panel_size.x += panel_bg_padding * 2; // restore initial width
1513 bool Scoreboard_WouldDraw()
1515 if (scoreboard_ui_enabled)
1517 else if (MUTATOR_CALLHOOK(DrawScoreboard))
1519 else if (QuickMenu_IsOpened())
1521 else if (HUD_Radar_Clickable())
1523 else if (scoreboard_showscores)
1525 else if (intermission == 1)
1527 else if (intermission == 2)
1529 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1530 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1534 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1539 float average_accuracy;
1540 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1542 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1544 WepSet weapons_stat = WepSet_GetFromStat();
1545 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1546 int disownedcnt = 0;
1548 FOREACH(Weapons, it != WEP_Null, {
1549 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1551 WepSet set = it.m_wepset;
1552 if(it.spawnflags & WEP_TYPE_OTHER)
1557 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1559 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1566 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1567 if (weapon_cnt <= 0) return pos;
1570 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1572 int columns = ceil(weapon_cnt / rows);
1574 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1575 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1576 float height = weapon_height + hud_fontsize.y;
1578 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);
1579 pos.y += 1.25 * hud_fontsize.y;
1580 if(panel.current_panel_bg != "0")
1581 pos.y += panel_bg_border;
1584 panel_size.y = height * rows;
1585 panel_size.y += panel_bg_padding * 2;
1587 float panel_bg_alpha_save = panel_bg_alpha;
1588 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1590 panel_bg_alpha = panel_bg_alpha_save;
1592 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1593 if(panel.current_panel_bg != "0")
1594 end_pos.y += panel_bg_border * 2;
1596 if(panel_bg_padding)
1598 panel_pos += '1 1 0' * panel_bg_padding;
1599 panel_size -= '2 2 0' * panel_bg_padding;
1603 vector tmp = panel_size;
1605 float weapon_width = tmp.x / columns / rows;
1608 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1612 // column highlighting
1613 for (int i = 0; i < columns; ++i)
1615 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);
1618 for (int i = 0; i < rows; ++i)
1619 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1622 average_accuracy = 0;
1623 int weapons_with_stats = 0;
1625 pos.x += weapon_width / 2;
1627 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1630 Accuracy_LoadColors();
1632 float oldposx = pos.x;
1636 FOREACH(Weapons, it != WEP_Null, {
1637 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1639 WepSet set = it.m_wepset;
1640 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1642 if (it.spawnflags & WEP_TYPE_OTHER)
1646 if (weapon_stats >= 0)
1647 weapon_alpha = sbt_fg_alpha;
1649 weapon_alpha = 0.2 * sbt_fg_alpha;
1652 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1654 if (weapon_stats >= 0) {
1655 weapons_with_stats += 1;
1656 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1658 string s = sprintf("%d%%", weapon_stats * 100);
1659 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1661 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1662 rgb = Accuracy_GetColor(weapon_stats);
1664 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1666 tmpos.x += weapon_width * rows;
1667 pos.x += weapon_width * rows;
1668 if (rows == 2 && column == columns - 1) {
1676 if (weapons_with_stats)
1677 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1679 panel_size.x += panel_bg_padding * 2; // restore initial width
1684 bool is_item_filtered(entity it)
1686 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1688 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1691 if (it.instanceOfArmor || it.instanceOfHealth)
1693 int ha_mask = floor(mask) % 10;
1696 default: return false;
1697 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1698 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1699 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1700 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1703 if (it.instanceOfAmmo)
1705 int ammo_mask = floor(mask / 10) % 10;
1706 return (ammo_mask == 1);
1711 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1713 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1715 int disowned_cnt = 0;
1716 int uninteresting_cnt = 0;
1717 IL_EACH(default_order_items, true, {
1718 int q = g_inventory.inv_items[it.m_id];
1719 //q = 1; // debug: display all items
1720 if (is_item_filtered(it))
1721 ++uninteresting_cnt;
1725 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1726 int n = items_cnt - disowned_cnt;
1727 if (n <= 0) return pos;
1729 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1730 int columns = max(6, ceil(n / rows));
1732 float item_height = hud_fontsize.y * 2.3;
1733 float height = item_height + hud_fontsize.y;
1735 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1736 pos.y += 1.25 * hud_fontsize.y;
1737 if(panel.current_panel_bg != "0")
1738 pos.y += panel_bg_border;
1741 panel_size.y = height * rows;
1742 panel_size.y += panel_bg_padding * 2;
1744 float panel_bg_alpha_save = panel_bg_alpha;
1745 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1747 panel_bg_alpha = panel_bg_alpha_save;
1749 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1750 if(panel.current_panel_bg != "0")
1751 end_pos.y += panel_bg_border * 2;
1753 if(panel_bg_padding)
1755 panel_pos += '1 1 0' * panel_bg_padding;
1756 panel_size -= '2 2 0' * panel_bg_padding;
1760 vector tmp = panel_size;
1762 float item_width = tmp.x / columns / rows;
1765 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1769 // column highlighting
1770 for (int i = 0; i < columns; ++i)
1772 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);
1775 for (int i = 0; i < rows; ++i)
1776 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1780 pos.x += item_width / 2;
1782 float oldposx = pos.x;
1786 IL_EACH(default_order_items, !is_item_filtered(it), {
1787 int n = g_inventory.inv_items[it.m_id];
1788 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1789 if (n <= 0) continue;
1790 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);
1792 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1793 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1794 tmpos.x += item_width * rows;
1795 pos.x += item_width * rows;
1796 if (rows == 2 && column == columns - 1) {
1804 panel_size.x += panel_bg_padding * 2; // restore initial width
1809 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1811 pos.x += hud_fontsize.x * 0.25;
1812 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1813 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1814 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1816 pos.y += hud_fontsize.y;
1821 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1822 float stat_secrets_found, stat_secrets_total;
1823 float stat_monsters_killed, stat_monsters_total;
1827 // get monster stats
1828 stat_monsters_killed = STAT(MONSTERS_KILLED);
1829 stat_monsters_total = STAT(MONSTERS_TOTAL);
1831 // get secrets stats
1832 stat_secrets_found = STAT(SECRETS_FOUND);
1833 stat_secrets_total = STAT(SECRETS_TOTAL);
1835 // get number of rows
1836 if(stat_secrets_total)
1838 if(stat_monsters_total)
1841 // if no rows, return
1845 // draw table header
1846 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1847 pos.y += 1.25 * hud_fontsize.y;
1848 if(panel.current_panel_bg != "0")
1849 pos.y += panel_bg_border;
1852 panel_size.y = hud_fontsize.y * rows;
1853 panel_size.y += panel_bg_padding * 2;
1856 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1857 if(panel.current_panel_bg != "0")
1858 end_pos.y += panel_bg_border * 2;
1860 if(panel_bg_padding)
1862 panel_pos += '1 1 0' * panel_bg_padding;
1863 panel_size -= '2 2 0' * panel_bg_padding;
1867 vector tmp = panel_size;
1870 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1873 if(stat_monsters_total)
1875 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1876 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1880 if(stat_secrets_total)
1882 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1883 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1886 panel_size.x += panel_bg_padding * 2; // restore initial width
1890 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1893 RANKINGS_RECEIVED_CNT = 0;
1894 for (i=RANKINGS_CNT-1; i>=0; --i)
1896 ++RANKINGS_RECEIVED_CNT;
1898 if (RANKINGS_RECEIVED_CNT == 0)
1901 vector hl_rgb = rgb + '0.5 0.5 0.5';
1903 vector scoreboard_selected_hl_pos = pos;
1905 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1906 pos.y += 1.25 * hud_fontsize.y;
1907 if(panel.current_panel_bg != "0")
1908 pos.y += panel_bg_border;
1910 vector scoreboard_selected_hl_size = '0 0 0';
1911 scoreboard_selected_hl_size.x = scoreboard_right - scoreboard_left;
1912 scoreboard_selected_hl_size.y = pos.y - scoreboard_selected_hl_pos.y;
1917 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1919 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1924 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1926 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1930 float ranksize = 3 * hud_fontsize.x;
1931 float timesize = 5 * hud_fontsize.x;
1932 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1933 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1934 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1937 rankings_cnt = RANKINGS_RECEIVED_CNT;
1938 rankings_rows = ceil(rankings_cnt / rankings_columns);
1941 // expand name column to fill the entire row
1942 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1943 namesize += available_space;
1944 columnsize.x += available_space;
1946 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1947 panel_size.y += panel_bg_padding * 2;
1948 scoreboard_selected_hl_size.y += panel_size.y;
1952 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1953 if(panel.current_panel_bg != "0")
1954 end_pos.y += panel_bg_border * 2;
1956 if(panel_bg_padding)
1958 panel_pos += '1 1 0' * panel_bg_padding;
1959 panel_size -= '2 2 0' * panel_bg_padding;
1965 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1967 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1969 int column = 0, j = 0;
1970 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1971 int start_item = rankings_start_column * rankings_rows;
1972 for(i = start_item; i < start_item + rankings_cnt; ++i)
1974 int t = grecordtime[i];
1978 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1979 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1980 else if(!((j + rankings_start_column + column) & 1) && sbt_highlight)
1981 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1983 str = count_ordinal(i+1);
1984 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1985 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1986 str = ColorTranslateRGB(grecordholder[i]);
1988 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1989 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1991 pos.y += 1.25 * hud_fontsize.y;
1993 if(j >= rankings_rows)
1997 pos.x += panel_size.x / rankings_columns;
1998 pos.y = panel_pos.y;
2001 strfree(zoned_name_self);
2003 if (scoreboard_selected_panel == SB_PANEL_RANKINGS)
2005 float fade = max(0, (1 - (time - scoreboard_selected_panel_time) * 2));
2006 drawfill(scoreboard_selected_hl_pos, scoreboard_selected_hl_size, '1 1 1', fade * 0.44, DRAWFLAG_NORMAL);
2009 panel_size.x += panel_bg_padding * 2; // restore initial width
2013 bool have_weapon_stats;
2014 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
2016 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
2018 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
2021 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
2022 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
2028 if (!have_weapon_stats)
2030 FOREACH(Weapons, it != WEP_Null, {
2031 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
2032 if (weapon_stats >= 0)
2034 have_weapon_stats = true;
2038 if (!have_weapon_stats)
2045 bool have_item_stats;
2046 bool Scoreboard_ItemStats_WouldDraw(float ypos)
2048 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
2050 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
2053 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
2054 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
2060 if (!have_item_stats)
2062 IL_EACH(default_order_items, true, {
2063 if (!is_item_filtered(it))
2065 int q = g_inventory.inv_items[it.m_id];
2066 //q = 1; // debug: display all items
2069 have_item_stats = true;
2074 if (!have_item_stats)
2081 vector Scoreboard_Spectators_Draw(vector pos) {
2086 for(pl = players.sort_next; pl; pl = pl.sort_next)
2088 if(pl.team == NUM_SPECTATOR)
2090 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2091 if(tm.team == NUM_SPECTATOR)
2093 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2094 draw_beginBoldFont();
2095 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2097 pos.y += 1.25 * hud_fontsize.y;
2099 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2100 pos.y += 1.25 * hud_fontsize.y;
2105 if (str != "") // if there's at least one spectator
2106 pos.y += 0.5 * hud_fontsize.y;
2111 void Scoreboard_Draw()
2113 if(!autocvar__hud_configure)
2115 if(!hud_draw_maximized) return;
2117 // frametime checks allow to toggle the scoreboard even when the game is paused
2118 if(scoreboard_active) {
2119 if (scoreboard_fade_alpha == 0)
2120 scoreboard_time = time;
2121 if(hud_configure_menu_open == 1)
2122 scoreboard_fade_alpha = 1;
2123 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
2124 if (scoreboard_fadeinspeed && frametime)
2125 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
2127 scoreboard_fade_alpha = 1;
2128 if(hud_fontsize_str != autocvar_hud_fontsize)
2130 hud_fontsize = HUD_GetFontsize("hud_fontsize");
2131 Scoreboard_initFieldSizes();
2132 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
2136 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
2137 if (scoreboard_fadeoutspeed && frametime)
2138 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
2140 scoreboard_fade_alpha = 0;
2143 if (!scoreboard_fade_alpha)
2145 scoreboard_acc_fade_alpha = 0;
2146 scoreboard_itemstats_fade_alpha = 0;
2151 scoreboard_fade_alpha = 0;
2153 if (autocvar_hud_panel_scoreboard_dynamichud)
2156 HUD_Scale_Disable();
2158 if(scoreboard_fade_alpha <= 0)
2160 panel_fade_alpha *= scoreboard_fade_alpha;
2161 HUD_Panel_LoadCvars();
2163 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
2164 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
2165 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
2166 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
2167 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
2168 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
2169 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
2171 // don't overlap with con_notify
2172 if(!autocvar__hud_configure)
2173 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
2175 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
2176 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
2177 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
2178 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
2179 panel_pos.x = scoreboard_left;
2180 panel_size.x = fixed_scoreboard_width;
2182 Scoreboard_UpdatePlayerTeams();
2184 scoreboard_top = panel_pos.y;
2185 vector pos = panel_pos;
2190 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
2192 // Begin of Game Info Section
2193 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
2194 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
2196 // Game Info: Game Type
2197 str = MapInfo_Type_ToText(gametype);
2198 if (scoreboard_ui_enabled == 2)
2200 if (scoreboard_selected_team)
2201 str = sprintf(_("^7Press ^3%s ^7to join the selected team"), getcommandkey(_("jump"), "+jump"));
2203 str = sprintf(_("^7Press ^3%s ^7to join 'best' team (auto-select)"), getcommandkey(_("jump"), "+jump"));
2205 draw_beginBoldFont();
2206 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);
2209 pos.y += sb_gameinfo_type_fontsize.y;
2210 // Game Info: Game Detail
2211 if (scoreboard_ui_enabled == 2)
2213 str = sprintf(_("^7Press ^3%s ^7to select a specific team"), translate_key("TAB"));
2214 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2218 float tl = STAT(TIMELIMIT);
2219 float fl = STAT(FRAGLIMIT);
2220 float ll = STAT(LEADLIMIT);
2221 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
2224 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
2225 if(!gametype.m_hidelimits)
2230 str = strcat(str, "^7 / "); // delimiter
2233 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
2234 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2235 (teamscores_label(ts_primary) == "fastest") ? "" :
2236 TranslateScoresLabel(teamscores_label(ts_primary))));
2240 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2241 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2242 (scores_label(ps_primary) == "fastest") ? "" :
2243 TranslateScoresLabel(scores_label(ps_primary))));
2248 if(tl > 0 || fl > 0)
2251 if (ll_and_fl && fl > 0)
2252 str = strcat(str, "^7 & ");
2254 str = strcat(str, "^7 / ");
2259 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2260 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2261 (teamscores_label(ts_primary) == "fastest") ? "" :
2262 TranslateScoresLabel(teamscores_label(ts_primary))));
2266 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2267 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2268 (scores_label(ps_primary) == "fastest") ? "" :
2269 TranslateScoresLabel(scores_label(ps_primary))));
2273 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
2275 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2276 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2278 // End of Game Info Section
2280 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2281 if(panel.current_panel_bg != "0")
2282 pos.y += panel_bg_border;
2284 // Draw the scoreboard
2285 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2288 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2292 vector panel_bg_color_save = panel_bg_color;
2293 vector team_score_baseoffset;
2294 vector team_size_baseoffset;
2295 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2297 // put team score to the left of scoreboard (and team size to the right)
2298 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2299 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2300 if(panel.current_panel_bg != "0")
2302 team_score_baseoffset.x -= panel_bg_border;
2303 team_size_baseoffset.x += panel_bg_border;
2308 // put team score to the right of scoreboard (and team size to the left)
2309 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2310 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2311 if(panel.current_panel_bg != "0")
2313 team_score_baseoffset.x += panel_bg_border;
2314 team_size_baseoffset.x -= panel_bg_border;
2318 int team_size_total = 0;
2319 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2321 // calculate team size total (sum of all team sizes)
2322 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2323 if(tm.team != NUM_SPECTATOR)
2324 team_size_total += tm.team_size;
2327 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2329 if(tm.team == NUM_SPECTATOR)
2334 draw_beginBoldFont();
2335 vector rgb = Team_ColorRGB(tm.team);
2336 str = ftos(tm.(teamscores(ts_primary)));
2337 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2339 // team score on the left (default)
2340 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2344 // team score on the right
2345 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2347 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2349 // team size (if set to show on the side)
2350 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2352 // calculate the starting position for the whole team size info string
2353 str = sprintf("%d/%d", tm.team_size, team_size_total);
2354 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2356 // team size on the left
2357 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2361 // team size on the right
2362 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2364 str = sprintf("%d", tm.team_size);
2365 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2366 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2367 str = sprintf("/%d", team_size_total);
2368 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2372 // secondary score, e.g. keyhunt
2373 if(ts_primary != ts_secondary)
2375 str = ftos(tm.(teamscores(ts_secondary)));
2376 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2379 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2384 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2387 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2390 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2391 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2392 else if(panel_bg_color_team > 0)
2393 panel_bg_color = rgb * panel_bg_color_team;
2395 panel_bg_color = rgb;
2396 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2398 panel_bg_color = panel_bg_color_save;
2402 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2403 if(tm.team != NUM_SPECTATOR)
2406 // display it anyway
2407 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2410 // draw scoreboard spectators before accuracy and item stats
2411 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2412 pos = Scoreboard_Spectators_Draw(pos);
2415 // draw accuracy and item stats
2416 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2417 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2418 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2419 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2421 // draw scoreboard spectators after accuracy and item stats and before rankings
2422 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2423 pos = Scoreboard_Spectators_Draw(pos);
2426 if(MUTATOR_CALLHOOK(ShowRankings)) {
2427 string ranktitle = M_ARGV(0, string);
2428 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2429 if(race_speedaward_alltimebest)
2432 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
2436 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_holder), namesize, hud_fontsize, stringwidth_colors);
2437 str = sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, name);
2438 str = strcat(str, " / ");
2440 name = textShortenToWidth(ColorTranslateRGB(race_speedaward_alltimebest_holder), namesize, hud_fontsize, stringwidth_colors);
2441 str = strcat(str, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, name));
2442 drawcolorcodedstring(pos, str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2443 pos.y += 1.25 * hud_fontsize.y; // line height + line spacing
2445 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2450 // draw scoreboard spectators after rankings
2451 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2452 pos = Scoreboard_Spectators_Draw(pos);
2455 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2457 // draw scoreboard spectators after mapstats
2458 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2459 pos = Scoreboard_Spectators_Draw(pos);
2463 // print information about respawn status
2464 float respawn_time = STAT(RESPAWN_TIME);
2468 if(respawn_time < 0)
2470 // a negative number means we are awaiting respawn, time value is still the same
2471 respawn_time *= -1; // remove mark now that we checked it
2473 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2474 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2476 str = sprintf(_("^1Respawning in ^3%s^1..."),
2477 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2478 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2480 count_seconds(ceil(respawn_time - time))
2484 else if(time < respawn_time)
2486 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2487 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2488 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2490 count_seconds(ceil(respawn_time - time))
2494 else if(time >= respawn_time)
2495 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2497 pos.y += 1.2 * hud_fontsize.y;
2498 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2501 pos.y += hud_fontsize.y;
2502 if (scoreboard_fade_alpha < 1)
2503 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2504 else if (pos.y != scoreboard_bottom)
2506 if (pos.y > scoreboard_bottom)
2507 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2509 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2514 if (scoreboard_fade_alpha == 1)
2516 if (scoreboard_bottom > 0.95 * vid_conheight)
2517 rankings_rows = max(1, rankings_rows - 1);
2518 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2519 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2521 rankings_cnt = rankings_rows * rankings_columns;