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 // mode 0: returns translated label
112 // mode 1: prints name and description of all the labels
113 string Label_getInfo(string label, int mode)
116 label = "bckills"; // first case in the switch
120 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
121 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
122 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")));
123 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
124 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
125 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
126 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
127 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
128 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
129 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
130 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
131 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
132 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
133 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
134 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
135 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
136 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
137 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
138 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
139 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
140 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
141 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
142 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
143 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
144 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
145 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
146 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
147 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")));
148 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
149 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
150 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
151 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
152 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
153 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
154 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
155 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
156 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
157 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
158 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
159 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
160 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
161 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
162 default: return label;
167 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
168 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
170 #define SB_EXTRA_SORTING_FIELDS 5
171 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
172 void Scoreboard_InitScores()
176 ps_primary = ps_secondary = NULL;
177 ts_primary = ts_secondary = -1;
178 FOREACH(Scores, true, {
179 if(scores_flags(it) & SFL_NOT_SORTABLE)
181 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
182 if(f == SFL_SORT_PRIO_PRIMARY)
184 if(f == SFL_SORT_PRIO_SECONDARY)
186 if(ps_primary == it || ps_secondary == it)
188 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
189 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
190 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
191 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
192 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
194 if(ps_secondary == NULL)
195 ps_secondary = ps_primary;
197 for(i = 0; i < MAX_TEAMSCORE; ++i)
199 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
200 if(f == SFL_SORT_PRIO_PRIMARY)
202 if(f == SFL_SORT_PRIO_SECONDARY)
205 if(ts_secondary == -1)
206 ts_secondary = ts_primary;
208 Cmd_Scoreboard_SetFields(0);
212 void Scoreboard_UpdatePlayerTeams()
214 static float update_time;
215 if (time <= update_time)
221 for(pl = players.sort_next; pl; pl = pl.sort_next)
224 int Team = entcs_GetScoreTeam(pl.sv_entnum);
225 if(SetTeam(pl, Team))
228 Scoreboard_UpdatePlayerPos(pl);
232 pl = players.sort_next;
237 print(strcat("PNUM: ", ftos(num), "\n"));
242 int Scoreboard_CompareScore(int vl, int vr, int f)
244 TC(int, vl); TC(int, vr); TC(int, f);
245 if(f & SFL_ZERO_IS_WORST)
247 if(vl == 0 && vr != 0)
249 if(vl != 0 && vr == 0)
253 return IS_INCREASING(f);
255 return IS_DECREASING(f);
259 float Scoreboard_ComparePlayerScores(entity left, entity right)
261 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
262 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
269 if(vl == NUM_SPECTATOR)
271 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
273 if(!left.gotscores && right.gotscores)
280 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
284 if (!fld) fld = ps_primary;
285 else if (ps_secondary == ps_primary) continue;
286 else fld = ps_secondary;
290 fld = sb_extra_sorting_field[i];
291 if (fld == ps_primary || fld == ps_secondary) continue;
295 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
296 if (r >= 0) return r;
299 if (left.sv_entnum < right.sv_entnum)
305 void Scoreboard_UpdatePlayerPos(entity player)
308 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
310 SORT_SWAP(player, ent);
312 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
314 SORT_SWAP(ent, player);
318 float Scoreboard_CompareTeamScores(entity left, entity right)
320 if(left.team == NUM_SPECTATOR)
322 if(right.team == NUM_SPECTATOR)
327 for(int i = -2; i < MAX_TEAMSCORE; ++i)
331 if (fld_idx == -1) fld_idx = ts_primary;
332 else if (ts_secondary == ts_primary) continue;
333 else fld_idx = ts_secondary;
338 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
341 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
342 if (r >= 0) return r;
345 if (left.team < right.team)
351 void Scoreboard_UpdateTeamPos(entity Team)
354 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
356 SORT_SWAP(Team, ent);
358 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
360 SORT_SWAP(ent, Team);
364 void Cmd_Scoreboard_Help()
366 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
367 LOG_HELP(_("Usage:"));
368 LOG_HELP("^2scoreboard_columns_set ^3default");
369 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
370 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
371 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
372 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
373 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
374 LOG_HELP(_("The following field names are recognized (case insensitive):"));
380 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
381 "of game types, then a slash, to make the field show up only in these\n"
382 "or in all but these game types. You can also specify 'all' as a\n"
383 "field to show all fields available for the current game mode."));
386 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
387 "include/exclude ALL teams/noteams game modes."));
390 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
391 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
392 "right of the vertical bar aligned to the right."));
393 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
394 "other gamemodes except DM."));
397 // NOTE: adding a gametype with ? to not warn for an optional field
398 // make sure it's excluded in a previous exclusive rule, if any
399 // otherwise the previous exclusive rule warns anyway
400 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
401 #define SCOREBOARD_DEFAULT_COLUMNS \
402 "ping pl fps name |" \
403 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
404 " -teams,lms/deaths +ft,tdm/deaths" \
406 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
407 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
408 " +tdm,ft,dom,ons,as/teamkills"\
409 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
410 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
411 " +lms/lives +lms/rank" \
412 " +kh/kckills +kh/losses +kh/caps" \
413 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
414 " +as/objectives +nb/faults +nb/goals" \
415 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
416 " +dom/ticks +dom/takes" \
417 " -lms,rc,cts,inv,nb/score"
419 void Cmd_Scoreboard_SetFields(int argc)
424 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
428 return; // do nothing, we don't know gametype and scores yet
430 // sbt_fields uses strunzone on the titles!
431 if(!sbt_field_title[0])
432 for(i = 0; i < MAX_SBT_FIELDS; ++i)
433 sbt_field_title[i] = strzone("(null)");
435 // TODO: re enable with gametype dependant cvars?
436 if(argc < 3) // no arguments provided
437 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
440 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
444 if(argv(2) == "default" || argv(2) == "expand_default")
446 if(argv(2) == "expand_default")
447 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
448 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
450 else if(argv(2) == "all" || argv(2) == "ALL")
452 string s = "ping pl name |"; // scores without label (not really scores)
455 // scores without label
456 s = strcat(s, " ", "sum");
457 s = strcat(s, " ", "kdratio");
458 s = strcat(s, " ", "frags");
460 FOREACH(Scores, true, {
462 if(it != ps_secondary)
463 if(scores_label(it) != "")
464 s = strcat(s, " ", scores_label(it));
466 if(ps_secondary != ps_primary)
467 s = strcat(s, " ", scores_label(ps_secondary));
468 s = strcat(s, " ", scores_label(ps_primary));
469 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
476 hud_fontsize = HUD_GetFontsize("hud_fontsize");
478 for(i = 1; i < argc - 1; ++i)
481 bool nocomplain = false;
482 if(substring(str, 0, 1) == "?")
485 str = substring(str, 1, strlen(str) - 1);
488 slash = strstrofs(str, "/", 0);
491 pattern = substring(str, 0, slash);
492 str = substring(str, slash + 1, strlen(str) - (slash + 1));
494 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
498 str = strtolower(str);
499 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
500 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
505 // fields without a label (not networked via the score system)
506 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
507 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
508 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
509 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
510 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
511 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
512 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
513 default: // fields with a label
515 // map alternative labels
516 if (str == "damage") str = "dmg";
517 if (str == "damagetaken") str = "dmgtaken";
519 FOREACH(Scores, true, {
520 if (str == strtolower(scores_label(it))) {
522 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
526 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
527 if(!nocomplain && str != "fps") // server can disable the fps field
528 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
530 strfree(sbt_field_title[sbt_num_fields]);
531 sbt_field_size[sbt_num_fields] = 0;
535 sbt_field[sbt_num_fields] = j;
538 if(j == ps_secondary)
539 have_secondary = true;
544 if(sbt_num_fields >= MAX_SBT_FIELDS)
548 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
550 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
551 have_secondary = true;
552 if(ps_primary == ps_secondary)
553 have_secondary = true;
554 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
556 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
560 strfree(sbt_field_title[sbt_num_fields]);
561 for(i = sbt_num_fields; i > 0; --i)
563 sbt_field_title[i] = sbt_field_title[i-1];
564 sbt_field_size[i] = sbt_field_size[i-1];
565 sbt_field[i] = sbt_field[i-1];
567 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
568 sbt_field[0] = SP_NAME;
570 LOG_INFO("fixed missing field 'name'");
574 strfree(sbt_field_title[sbt_num_fields]);
575 for(i = sbt_num_fields; i > 1; --i)
577 sbt_field_title[i] = sbt_field_title[i-1];
578 sbt_field_size[i] = sbt_field_size[i-1];
579 sbt_field[i] = sbt_field[i-1];
581 sbt_field_title[1] = strzone("|");
582 sbt_field[1] = SP_SEPARATOR;
583 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
585 LOG_INFO("fixed missing field '|'");
588 else if(!have_separator)
590 strcpy(sbt_field_title[sbt_num_fields], "|");
591 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
592 sbt_field[sbt_num_fields] = SP_SEPARATOR;
594 LOG_INFO("fixed missing field '|'");
598 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
599 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
600 sbt_field[sbt_num_fields] = ps_secondary;
602 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
606 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
607 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
608 sbt_field[sbt_num_fields] = ps_primary;
610 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
614 sbt_field[sbt_num_fields] = SP_END;
617 string Scoreboard_AddPlayerId(string pl_name, entity pl)
619 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
620 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
621 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
625 vector sbt_field_rgb;
626 string sbt_field_icon0;
627 string sbt_field_icon1;
628 string sbt_field_icon2;
629 vector sbt_field_icon0_rgb;
630 vector sbt_field_icon1_rgb;
631 vector sbt_field_icon2_rgb;
632 string Scoreboard_GetName(entity pl)
634 if(ready_waiting && pl.ready)
636 sbt_field_icon0 = "gfx/scoreboard/player_ready";
640 int f = entcs_GetClientColors(pl.sv_entnum);
642 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
643 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
644 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
645 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
646 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
649 return entcs_GetName(pl.sv_entnum);
652 string Scoreboard_GetField(entity pl, PlayerScoreField field)
654 float tmp, num, denom;
657 sbt_field_rgb = '1 1 1';
658 sbt_field_icon0 = "";
659 sbt_field_icon1 = "";
660 sbt_field_icon2 = "";
661 sbt_field_icon0_rgb = '1 1 1';
662 sbt_field_icon1_rgb = '1 1 1';
663 sbt_field_icon2_rgb = '1 1 1';
668 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
669 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
673 tmp = max(0, min(220, f-80)) / 220;
674 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
680 f = pl.ping_packetloss;
681 tmp = pl.ping_movementloss;
682 if(f == 0 && tmp == 0)
684 str = ftos(ceil(f * 100));
686 str = strcat(str, "~", ftos(ceil(tmp * 100)));
687 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
688 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
692 str = Scoreboard_GetName(pl);
693 if (autocvar_hud_panel_scoreboard_playerid)
694 str = Scoreboard_AddPlayerId(str, pl);
698 f = pl.(scores(SP_KILLS));
699 f -= pl.(scores(SP_SUICIDES));
703 num = pl.(scores(SP_KILLS));
704 denom = pl.(scores(SP_DEATHS));
707 sbt_field_rgb = '0 1 0';
708 str = sprintf("%d", num);
709 } else if(num <= 0) {
710 sbt_field_rgb = '1 0 0';
711 str = sprintf("%.1f", num/denom);
713 str = sprintf("%.1f", num/denom);
717 f = pl.(scores(SP_KILLS));
718 f -= pl.(scores(SP_DEATHS));
721 sbt_field_rgb = '0 1 0';
723 sbt_field_rgb = '1 1 1';
725 sbt_field_rgb = '1 0 0';
731 float elo = pl.(scores(SP_ELO));
733 case -1: return "...";
734 case -2: return _("N/A");
735 default: return ftos(elo);
741 float fps = pl.(scores(SP_FPS));
744 sbt_field_rgb = '1 1 1';
745 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
747 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
748 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
752 case SP_DMG: case SP_DMGTAKEN:
753 return sprintf("%.1f k", pl.(scores(field)) / 1000);
755 default: case SP_SCORE:
756 tmp = pl.(scores(field));
757 f = scores_flags(field);
758 if(field == ps_primary)
759 sbt_field_rgb = '1 1 0';
760 else if(field == ps_secondary)
761 sbt_field_rgb = '0 1 1';
763 sbt_field_rgb = '1 1 1';
764 return ScoreString(f, tmp);
769 float sbt_fixcolumnwidth_len;
770 float sbt_fixcolumnwidth_iconlen;
771 float sbt_fixcolumnwidth_marginlen;
773 string Scoreboard_FixColumnWidth(int i, string str)
779 sbt_fixcolumnwidth_iconlen = 0;
781 if(sbt_field_icon0 != "")
783 sz = draw_getimagesize(sbt_field_icon0);
785 if(sbt_fixcolumnwidth_iconlen < f)
786 sbt_fixcolumnwidth_iconlen = f;
789 if(sbt_field_icon1 != "")
791 sz = draw_getimagesize(sbt_field_icon1);
793 if(sbt_fixcolumnwidth_iconlen < f)
794 sbt_fixcolumnwidth_iconlen = f;
797 if(sbt_field_icon2 != "")
799 sz = draw_getimagesize(sbt_field_icon2);
801 if(sbt_fixcolumnwidth_iconlen < f)
802 sbt_fixcolumnwidth_iconlen = f;
805 if(sbt_fixcolumnwidth_iconlen != 0)
807 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
808 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
811 sbt_fixcolumnwidth_marginlen = 0;
813 if(sbt_field[i] == SP_NAME) // name gets all remaining space
816 float remaining_space = 0;
817 for(j = 0; j < sbt_num_fields; ++j)
819 if (sbt_field[i] != SP_SEPARATOR)
820 remaining_space += sbt_field_size[j] + hud_fontsize.x;
821 sbt_field_size[i] = panel_size.x - remaining_space;
823 if (sbt_fixcolumnwidth_iconlen != 0)
824 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
825 float namesize = panel_size.x - remaining_space;
826 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
827 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
829 max_namesize = vid_conwidth - remaining_space;
832 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
834 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
835 if(sbt_field_size[i] < f)
836 sbt_field_size[i] = f;
841 void Scoreboard_initFieldSizes()
843 for(int i = 0; i < sbt_num_fields; ++i)
845 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
846 Scoreboard_FixColumnWidth(i, "");
850 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
853 vector column_dim = eY * panel_size.y;
855 column_dim.y -= 1.25 * hud_fontsize.y;
856 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
857 pos.x += hud_fontsize.x * 0.5;
858 for(i = 0; i < sbt_num_fields; ++i)
860 if(sbt_field[i] == SP_SEPARATOR)
862 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
865 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
866 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
867 pos.x += column_dim.x;
869 if(sbt_field[i] == SP_SEPARATOR)
871 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
872 for(i = sbt_num_fields - 1; i > 0; --i)
874 if(sbt_field[i] == SP_SEPARATOR)
877 pos.x -= sbt_field_size[i];
882 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
883 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
886 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
887 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
888 pos.x -= hud_fontsize.x;
893 pos.y += 1.25 * hud_fontsize.y;
897 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
899 TC(bool, is_self); TC(int, pl_number);
901 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
903 vector h_pos = item_pos;
904 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
905 // alternated rows highlighting
907 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
908 else if((sbt_highlight) && (!(pl_number % 2)))
909 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
911 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
913 vector pos = item_pos;
914 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
916 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
918 pos.x += hud_fontsize.x * 0.5;
919 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
920 vector tmp = '0 0 0';
922 PlayerScoreField field;
923 for(i = 0; i < sbt_num_fields; ++i)
925 field = sbt_field[i];
926 if(field == SP_SEPARATOR)
929 if(is_spec && field != SP_NAME && field != SP_PING) {
930 pos.x += sbt_field_size[i] + hud_fontsize.x;
933 str = Scoreboard_GetField(pl, field);
934 str = Scoreboard_FixColumnWidth(i, str);
936 pos.x += sbt_field_size[i] + hud_fontsize.x;
938 if(field == SP_NAME) {
939 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
940 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
942 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
943 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
946 tmp.x = sbt_field_size[i] + hud_fontsize.x;
947 if(sbt_field_icon0 != "")
948 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
949 if(sbt_field_icon1 != "")
950 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
951 if(sbt_field_icon2 != "")
952 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
955 if(sbt_field[i] == SP_SEPARATOR)
957 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
958 for(i = sbt_num_fields-1; i > 0; --i)
960 field = sbt_field[i];
961 if(field == SP_SEPARATOR)
964 if(is_spec && field != SP_NAME && field != SP_PING) {
965 pos.x -= sbt_field_size[i] + hud_fontsize.x;
969 str = Scoreboard_GetField(pl, field);
970 str = Scoreboard_FixColumnWidth(i, str);
972 if(field == SP_NAME) {
973 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
974 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
976 tmp.x = sbt_fixcolumnwidth_len;
977 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
980 tmp.x = sbt_field_size[i];
981 if(sbt_field_icon0 != "")
982 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
983 if(sbt_field_icon1 != "")
984 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
985 if(sbt_field_icon2 != "")
986 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
987 pos.x -= sbt_field_size[i] + hud_fontsize.x;
992 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
995 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
998 vector h_pos = item_pos;
999 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
1001 bool complete = (this_team == NUM_SPECTATOR);
1004 if((sbt_highlight) && (!(pl_number % 2)))
1005 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1007 vector pos = item_pos;
1008 pos.x += hud_fontsize.x * 0.5;
1009 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1011 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
1013 width_limit -= stringwidth("...", false, hud_fontsize);
1014 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1015 static float max_name_width = 0;
1017 float fieldsize = 0;
1018 float min_fieldsize = 0;
1019 float fieldpadding = hud_fontsize.x * 0.25;
1020 if(this_team == NUM_SPECTATOR)
1022 if(autocvar_hud_panel_scoreboard_spectators_showping)
1023 min_fieldsize = stringwidth("999", false, hud_fontsize);
1025 else if(autocvar_hud_panel_scoreboard_others_showscore)
1026 min_fieldsize = stringwidth("99", false, hud_fontsize);
1027 for(i = 0; pl; pl = pl.sort_next)
1029 if(pl.team != this_team)
1031 if(pl == ignored_pl)
1035 if(this_team == NUM_SPECTATOR)
1037 if(autocvar_hud_panel_scoreboard_spectators_showping)
1038 field = Scoreboard_GetField(pl, SP_PING);
1040 else if(autocvar_hud_panel_scoreboard_others_showscore)
1041 field = Scoreboard_GetField(pl, SP_SCORE);
1043 string str = entcs_GetName(pl.sv_entnum);
1044 if (autocvar_hud_panel_scoreboard_playerid)
1045 str = Scoreboard_AddPlayerId(str, pl);
1046 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1047 float column_width = stringwidth(str, true, hud_fontsize);
1048 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1050 if(column_width > max_name_width)
1051 max_name_width = column_width;
1052 column_width = max_name_width;
1056 fieldsize = stringwidth(field, false, hud_fontsize);
1057 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1060 if(pos.x + column_width > width_limit)
1065 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1070 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1071 pos.y += hud_fontsize.y * 1.25;
1075 vector name_pos = pos;
1076 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1077 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1078 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1081 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1082 h_size.y = hud_fontsize.y;
1083 vector field_pos = pos;
1084 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1085 field_pos.x += column_width - h_size.x;
1087 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1088 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1089 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1093 h_size.x = column_width + hud_fontsize.x * 0.25;
1094 h_size.y = hud_fontsize.y;
1095 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1097 pos.x += column_width;
1098 pos.x += hud_fontsize.x;
1100 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1103 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1105 int max_players = 999;
1106 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1108 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1111 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1112 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1113 height /= team_count;
1116 height -= panel_bg_padding * 2; // - padding
1117 max_players = floor(height / (hud_fontsize.y * 1.25));
1118 if(max_players <= 1)
1120 if(max_players == tm.team_size)
1125 entity me = playerslots[current_player];
1127 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1128 panel_size.y += panel_bg_padding * 2;
1131 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1132 if(panel.current_panel_bg != "0")
1133 end_pos.y += panel_bg_border * 2;
1135 if(panel_bg_padding)
1137 panel_pos += '1 1 0' * panel_bg_padding;
1138 panel_size -= '2 2 0' * panel_bg_padding;
1142 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1146 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1148 pos.y += 1.25 * hud_fontsize.y;
1151 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1153 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1156 // print header row and highlight columns
1157 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1159 // fill the table and draw the rows
1160 bool is_self = false;
1161 bool self_shown = false;
1163 for(pl = players.sort_next; pl; pl = pl.sort_next)
1165 if(pl.team != tm.team)
1167 if(i == max_players - 2 && pl != me)
1169 if(!self_shown && me.team == tm.team)
1171 Scoreboard_DrawItem(pos, rgb, me, true, i);
1173 pos.y += 1.25 * hud_fontsize.y;
1177 if(i >= max_players - 1)
1179 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1182 is_self = (pl.sv_entnum == current_player);
1183 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1186 pos.y += 1.25 * hud_fontsize.y;
1190 panel_size.x += panel_bg_padding * 2; // restore initial width
1194 bool Scoreboard_WouldDraw()
1196 if (MUTATOR_CALLHOOK(DrawScoreboard))
1198 else if (QuickMenu_IsOpened())
1200 else if (HUD_Radar_Clickable())
1202 else if (scoreboard_showscores)
1204 else if (intermission == 1)
1206 else if (intermission == 2)
1208 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1209 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1213 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1218 float average_accuracy;
1219 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1221 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1223 WepSet weapons_stat = WepSet_GetFromStat();
1224 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1225 int disownedcnt = 0;
1227 FOREACH(Weapons, it != WEP_Null, {
1228 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1230 WepSet set = it.m_wepset;
1231 if(it.spawnflags & WEP_TYPE_OTHER)
1236 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1238 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1245 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1246 if (weapon_cnt <= 0) return pos;
1249 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1251 int columns = ceil(weapon_cnt / rows);
1253 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1254 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1255 float height = weapon_height + hud_fontsize.y;
1257 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);
1258 pos.y += 1.25 * hud_fontsize.y;
1259 if(panel.current_panel_bg != "0")
1260 pos.y += panel_bg_border;
1263 panel_size.y = height * rows;
1264 panel_size.y += panel_bg_padding * 2;
1266 float panel_bg_alpha_save = panel_bg_alpha;
1267 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1269 panel_bg_alpha = panel_bg_alpha_save;
1271 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1272 if(panel.current_panel_bg != "0")
1273 end_pos.y += panel_bg_border * 2;
1275 if(panel_bg_padding)
1277 panel_pos += '1 1 0' * panel_bg_padding;
1278 panel_size -= '2 2 0' * panel_bg_padding;
1282 vector tmp = panel_size;
1284 float weapon_width = tmp.x / columns / rows;
1287 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1291 // column highlighting
1292 for (int i = 0; i < columns; ++i)
1294 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);
1297 for (int i = 0; i < rows; ++i)
1298 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1301 average_accuracy = 0;
1302 int weapons_with_stats = 0;
1304 pos.x += weapon_width / 2;
1306 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1309 Accuracy_LoadColors();
1311 float oldposx = pos.x;
1315 FOREACH(Weapons, it != WEP_Null, {
1316 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1318 WepSet set = it.m_wepset;
1319 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1321 if (it.spawnflags & WEP_TYPE_OTHER)
1325 if (weapon_stats >= 0)
1326 weapon_alpha = sbt_fg_alpha;
1328 weapon_alpha = 0.2 * sbt_fg_alpha;
1331 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1333 if (weapon_stats >= 0) {
1334 weapons_with_stats += 1;
1335 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1337 string s = sprintf("%d%%", weapon_stats * 100);
1338 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1340 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1341 rgb = Accuracy_GetColor(weapon_stats);
1343 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1345 tmpos.x += weapon_width * rows;
1346 pos.x += weapon_width * rows;
1347 if (rows == 2 && column == columns - 1) {
1355 if (weapons_with_stats)
1356 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1358 panel_size.x += panel_bg_padding * 2; // restore initial width
1363 bool is_item_filtered(entity it)
1365 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1367 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1370 if (it.instanceOfArmor || it.instanceOfHealth)
1372 int ha_mask = floor(mask) % 10;
1375 default: return false;
1376 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1377 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1378 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1379 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1382 if (it.instanceOfAmmo)
1384 int ammo_mask = floor(mask / 10) % 10;
1385 return (ammo_mask == 1);
1390 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1392 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1394 int disowned_cnt = 0;
1395 int uninteresting_cnt = 0;
1396 IL_EACH(default_order_items, true, {
1397 int q = g_inventory.inv_items[it.m_id];
1398 //q = 1; // debug: display all items
1399 if (is_item_filtered(it))
1400 ++uninteresting_cnt;
1404 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1405 int n = items_cnt - disowned_cnt;
1406 if (n <= 0) return pos;
1408 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1409 int columns = max(6, ceil(n / rows));
1411 float item_height = hud_fontsize.y * 2.3;
1412 float height = item_height + hud_fontsize.y;
1414 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1415 pos.y += 1.25 * hud_fontsize.y;
1416 if(panel.current_panel_bg != "0")
1417 pos.y += panel_bg_border;
1420 panel_size.y = height * rows;
1421 panel_size.y += panel_bg_padding * 2;
1423 float panel_bg_alpha_save = panel_bg_alpha;
1424 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1426 panel_bg_alpha = panel_bg_alpha_save;
1428 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1429 if(panel.current_panel_bg != "0")
1430 end_pos.y += panel_bg_border * 2;
1432 if(panel_bg_padding)
1434 panel_pos += '1 1 0' * panel_bg_padding;
1435 panel_size -= '2 2 0' * panel_bg_padding;
1439 vector tmp = panel_size;
1441 float item_width = tmp.x / columns / rows;
1444 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1448 // column highlighting
1449 for (int i = 0; i < columns; ++i)
1451 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);
1454 for (int i = 0; i < rows; ++i)
1455 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1459 pos.x += item_width / 2;
1461 float oldposx = pos.x;
1465 IL_EACH(default_order_items, !is_item_filtered(it), {
1466 int n = g_inventory.inv_items[it.m_id];
1467 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1468 if (n <= 0) continue;
1469 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);
1471 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1472 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1473 tmpos.x += item_width * rows;
1474 pos.x += item_width * rows;
1475 if (rows == 2 && column == columns - 1) {
1483 panel_size.x += panel_bg_padding * 2; // restore initial width
1488 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1490 pos.x += hud_fontsize.x * 0.25;
1491 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1492 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1493 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1495 pos.y += hud_fontsize.y;
1500 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1501 float stat_secrets_found, stat_secrets_total;
1502 float stat_monsters_killed, stat_monsters_total;
1506 // get monster stats
1507 stat_monsters_killed = STAT(MONSTERS_KILLED);
1508 stat_monsters_total = STAT(MONSTERS_TOTAL);
1510 // get secrets stats
1511 stat_secrets_found = STAT(SECRETS_FOUND);
1512 stat_secrets_total = STAT(SECRETS_TOTAL);
1514 // get number of rows
1515 if(stat_secrets_total)
1517 if(stat_monsters_total)
1520 // if no rows, return
1524 // draw table header
1525 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1526 pos.y += 1.25 * hud_fontsize.y;
1527 if(panel.current_panel_bg != "0")
1528 pos.y += panel_bg_border;
1531 panel_size.y = hud_fontsize.y * rows;
1532 panel_size.y += panel_bg_padding * 2;
1535 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1536 if(panel.current_panel_bg != "0")
1537 end_pos.y += panel_bg_border * 2;
1539 if(panel_bg_padding)
1541 panel_pos += '1 1 0' * panel_bg_padding;
1542 panel_size -= '2 2 0' * panel_bg_padding;
1546 vector tmp = panel_size;
1549 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1552 if(stat_monsters_total)
1554 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1555 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1559 if(stat_secrets_total)
1561 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1562 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1565 panel_size.x += panel_bg_padding * 2; // restore initial width
1569 int rankings_rows = 0;
1570 int rankings_columns = 0;
1571 int rankings_cnt = 0;
1572 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1575 RANKINGS_RECEIVED_CNT = 0;
1576 for (i=RANKINGS_CNT-1; i>=0; --i)
1578 ++RANKINGS_RECEIVED_CNT;
1580 if (RANKINGS_RECEIVED_CNT == 0)
1583 vector hl_rgb = rgb + '0.5 0.5 0.5';
1585 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1586 pos.y += 1.25 * hud_fontsize.y;
1587 if(panel.current_panel_bg != "0")
1588 pos.y += panel_bg_border;
1593 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1595 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1600 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1602 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1606 float ranksize = 3 * hud_fontsize.x;
1607 float timesize = 5 * hud_fontsize.x;
1608 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1609 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1610 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1613 rankings_cnt = RANKINGS_RECEIVED_CNT;
1614 rankings_rows = ceil(rankings_cnt / rankings_columns);
1617 // expand name column to fill the entire row
1618 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1619 namesize += available_space;
1620 columnsize.x += available_space;
1622 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1623 panel_size.y += panel_bg_padding * 2;
1627 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1628 if(panel.current_panel_bg != "0")
1629 end_pos.y += panel_bg_border * 2;
1631 if(panel_bg_padding)
1633 panel_pos += '1 1 0' * panel_bg_padding;
1634 panel_size -= '2 2 0' * panel_bg_padding;
1640 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1642 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1644 int column = 0, j = 0;
1645 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1646 for(i = 0; i < rankings_cnt; ++i)
1653 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1654 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1655 else if(!((j + column) & 1) && sbt_highlight)
1656 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1658 str = count_ordinal(i+1);
1659 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1660 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1661 str = ColorTranslateRGB(grecordholder[i]);
1663 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1664 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1666 pos.y += 1.25 * hud_fontsize.y;
1668 if(j >= rankings_rows)
1672 pos.x += panel_size.x / rankings_columns;
1673 pos.y = panel_pos.y;
1676 strfree(zoned_name_self);
1678 panel_size.x += panel_bg_padding * 2; // restore initial width
1682 float scoreboard_time;
1683 bool have_weapon_stats;
1684 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1686 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1688 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1691 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1692 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1698 if (!have_weapon_stats)
1700 FOREACH(Weapons, it != WEP_Null, {
1701 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1702 if (weapon_stats >= 0)
1704 have_weapon_stats = true;
1708 if (!have_weapon_stats)
1715 bool have_item_stats;
1716 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1718 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1720 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1723 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1724 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1730 if (!have_item_stats)
1732 IL_EACH(default_order_items, true, {
1733 if (!is_item_filtered(it))
1735 int q = g_inventory.inv_items[it.m_id];
1736 //q = 1; // debug: display all items
1739 have_item_stats = true;
1744 if (!have_item_stats)
1751 vector Scoreboard_Spectators_Draw(vector pos) {
1756 for(pl = players.sort_next; pl; pl = pl.sort_next)
1758 if(pl.team == NUM_SPECTATOR)
1760 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1761 if(tm.team == NUM_SPECTATOR)
1763 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1764 draw_beginBoldFont();
1765 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1767 pos.y += 1.25 * hud_fontsize.y;
1769 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1770 pos.y += 1.25 * hud_fontsize.y;
1775 if (str != "") // if there's at least one spectator
1776 pos.y += 0.5 * hud_fontsize.y;
1781 void Scoreboard_Draw()
1783 if(!autocvar__hud_configure)
1785 if(!hud_draw_maximized) return;
1787 // frametime checks allow to toggle the scoreboard even when the game is paused
1788 if(scoreboard_active) {
1789 if (scoreboard_fade_alpha == 0)
1790 scoreboard_time = time;
1791 if(hud_configure_menu_open == 1)
1792 scoreboard_fade_alpha = 1;
1793 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1794 if (scoreboard_fadeinspeed && frametime)
1795 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1797 scoreboard_fade_alpha = 1;
1798 if(hud_fontsize_str != autocvar_hud_fontsize)
1800 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1801 Scoreboard_initFieldSizes();
1802 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1806 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1807 if (scoreboard_fadeoutspeed && frametime)
1808 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1810 scoreboard_fade_alpha = 0;
1813 if (!scoreboard_fade_alpha)
1815 scoreboard_acc_fade_alpha = 0;
1816 scoreboard_itemstats_fade_alpha = 0;
1821 scoreboard_fade_alpha = 0;
1823 if (autocvar_hud_panel_scoreboard_dynamichud)
1826 HUD_Scale_Disable();
1828 if(scoreboard_fade_alpha <= 0)
1830 panel_fade_alpha *= scoreboard_fade_alpha;
1831 HUD_Panel_LoadCvars();
1833 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1834 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1835 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1836 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1837 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1838 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1839 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1841 // don't overlap with con_notify
1842 if(!autocvar__hud_configure)
1843 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1845 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1846 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1847 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1848 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1849 panel_pos.x = scoreboard_left;
1850 panel_size.x = fixed_scoreboard_width;
1852 Scoreboard_UpdatePlayerTeams();
1854 scoreboard_top = panel_pos.y;
1855 vector pos = panel_pos;
1860 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1862 // Begin of Game Info Section
1863 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1864 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1866 // Game Info: Game Type
1867 str = MapInfo_Type_ToText(gametype);
1868 draw_beginBoldFont();
1869 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);
1872 // Game Info: Game Detail
1873 float tl = STAT(TIMELIMIT);
1874 float fl = STAT(FRAGLIMIT);
1875 float ll = STAT(LEADLIMIT);
1876 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1879 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1880 if(!gametype.m_hidelimits)
1885 str = strcat(str, "^7 / "); // delimiter
1888 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1889 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1890 (teamscores_label(ts_primary) == "fastest") ? "" :
1891 TranslateScoresLabel(teamscores_label(ts_primary))));
1895 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1896 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1897 (scores_label(ps_primary) == "fastest") ? "" :
1898 TranslateScoresLabel(scores_label(ps_primary))));
1903 if(tl > 0 || fl > 0)
1906 if (ll_and_fl && fl > 0)
1907 str = strcat(str, "^7 & ");
1909 str = strcat(str, "^7 / ");
1914 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1915 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1916 (teamscores_label(ts_primary) == "fastest") ? "" :
1917 TranslateScoresLabel(teamscores_label(ts_primary))));
1921 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1922 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1923 (scores_label(ps_primary) == "fastest") ? "" :
1924 TranslateScoresLabel(scores_label(ps_primary))));
1929 pos.y += sb_gameinfo_type_fontsize.y;
1930 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
1932 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1933 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1934 // End of Game Info Section
1936 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1937 if(panel.current_panel_bg != "0")
1938 pos.y += panel_bg_border;
1940 // Draw the scoreboard
1941 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1944 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1948 vector panel_bg_color_save = panel_bg_color;
1949 vector team_score_baseoffset;
1950 vector team_size_baseoffset;
1951 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1953 // put team score to the left of scoreboard (and team size to the right)
1954 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1955 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1956 if(panel.current_panel_bg != "0")
1958 team_score_baseoffset.x -= panel_bg_border;
1959 team_size_baseoffset.x += panel_bg_border;
1964 // put team score to the right of scoreboard (and team size to the left)
1965 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1966 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1967 if(panel.current_panel_bg != "0")
1969 team_score_baseoffset.x += panel_bg_border;
1970 team_size_baseoffset.x -= panel_bg_border;
1974 int team_size_total = 0;
1975 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1977 // calculate team size total (sum of all team sizes)
1978 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1979 if(tm.team != NUM_SPECTATOR)
1980 team_size_total += tm.team_size;
1983 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1985 if(tm.team == NUM_SPECTATOR)
1990 draw_beginBoldFont();
1991 vector rgb = Team_ColorRGB(tm.team);
1992 str = ftos(tm.(teamscores(ts_primary)));
1993 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1995 // team score on the left (default)
1996 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2000 // team score on the right
2001 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2003 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2005 // team size (if set to show on the side)
2006 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2008 // calculate the starting position for the whole team size info string
2009 str = sprintf("%d/%d", tm.team_size, team_size_total);
2010 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2012 // team size on the left
2013 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2017 // team size on the right
2018 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2020 str = sprintf("%d", tm.team_size);
2021 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2022 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2023 str = sprintf("/%d", team_size_total);
2024 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2028 // secondary score, e.g. keyhunt
2029 if(ts_primary != ts_secondary)
2031 str = ftos(tm.(teamscores(ts_secondary)));
2032 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2035 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2040 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2043 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2046 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2047 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2048 else if(panel_bg_color_team > 0)
2049 panel_bg_color = rgb * panel_bg_color_team;
2051 panel_bg_color = rgb;
2052 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2054 panel_bg_color = panel_bg_color_save;
2058 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2059 if(tm.team != NUM_SPECTATOR)
2062 // display it anyway
2063 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2066 // draw scoreboard spectators before accuracy and item stats
2067 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2068 pos = Scoreboard_Spectators_Draw(pos);
2071 // draw accuracy and item stats
2072 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2073 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2074 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2075 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2077 // draw scoreboard spectators after accuracy and item stats and before rankings
2078 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2079 pos = Scoreboard_Spectators_Draw(pos);
2082 if(MUTATOR_CALLHOOK(ShowRankings)) {
2083 string ranktitle = M_ARGV(0, string);
2084 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2085 if(race_speedaward) {
2086 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2087 pos.y += 1.25 * hud_fontsize.y;
2089 if(race_speedaward_alltimebest) {
2090 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2091 pos.y += 1.25 * hud_fontsize.y;
2093 if (race_speedaward || race_speedaward_alltimebest)
2094 pos.y += 0.25 * hud_fontsize.y;
2095 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2100 // draw scoreboard spectators after rankings
2101 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2102 pos = Scoreboard_Spectators_Draw(pos);
2105 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2107 // draw scoreboard spectators after mapstats
2108 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2109 pos = Scoreboard_Spectators_Draw(pos);
2113 // print information about respawn status
2114 float respawn_time = STAT(RESPAWN_TIME);
2118 if(respawn_time < 0)
2120 // a negative number means we are awaiting respawn, time value is still the same
2121 respawn_time *= -1; // remove mark now that we checked it
2123 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2124 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2126 str = sprintf(_("^1Respawning in ^3%s^1..."),
2127 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2128 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2130 count_seconds(ceil(respawn_time - time))
2134 else if(time < respawn_time)
2136 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2137 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2138 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2140 count_seconds(ceil(respawn_time - time))
2144 else if(time >= respawn_time)
2145 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2147 pos.y += 1.2 * hud_fontsize.y;
2148 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2151 pos.y += hud_fontsize.y;
2152 if (scoreboard_fade_alpha < 1)
2153 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2154 else if (pos.y != scoreboard_bottom)
2156 if (pos.y > scoreboard_bottom)
2157 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2159 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2164 if (scoreboard_fade_alpha == 1)
2166 if (scoreboard_bottom > 0.95 * vid_conheight)
2167 rankings_rows = max(1, rankings_rows - 1);
2168 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2169 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2171 rankings_cnt = rankings_rows * rankings_columns;