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 void Scoreboard_InitScores()
174 ps_primary = ps_secondary = NULL;
175 ts_primary = ts_secondary = -1;
176 FOREACH(Scores, true, {
177 if(scores_flags(it) & SFL_NOT_SORTABLE)
179 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
180 if(f == SFL_SORT_PRIO_PRIMARY)
182 if(f == SFL_SORT_PRIO_SECONDARY)
185 if(ps_secondary == NULL)
186 ps_secondary = ps_primary;
188 for(i = 0; i < MAX_TEAMSCORE; ++i)
190 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
191 if(f == SFL_SORT_PRIO_PRIMARY)
193 if(f == SFL_SORT_PRIO_SECONDARY)
196 if(ts_secondary == -1)
197 ts_secondary = ts_primary;
199 Cmd_Scoreboard_SetFields(0);
203 void Scoreboard_UpdatePlayerTeams()
205 static float update_time;
206 if (time <= update_time)
212 for(pl = players.sort_next; pl; pl = pl.sort_next)
215 int Team = entcs_GetScoreTeam(pl.sv_entnum);
216 if(SetTeam(pl, Team))
219 Scoreboard_UpdatePlayerPos(pl);
223 pl = players.sort_next;
228 print(strcat("PNUM: ", ftos(num), "\n"));
233 int Scoreboard_CompareScore(int vl, int vr, int f)
235 TC(int, vl); TC(int, vr); TC(int, f);
236 if(f & SFL_ZERO_IS_WORST)
238 if(vl == 0 && vr != 0)
240 if(vl != 0 && vr == 0)
244 return IS_INCREASING(f);
246 return IS_DECREASING(f);
250 float Scoreboard_ComparePlayerScores(entity left, entity right)
252 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
253 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
260 if(vl == NUM_SPECTATOR)
262 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
264 if(!left.gotscores && right.gotscores)
269 int res = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
270 if (res >= 0) return res;
272 if (ps_secondary && ps_secondary != ps_primary)
274 res = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
275 if (res >= 0) return res;
278 FOREACH(Scores, (it != ps_primary && it != ps_secondary), {
279 res = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
280 if (res >= 0) return res;
283 if (left.sv_entnum < right.sv_entnum)
289 void Scoreboard_UpdatePlayerPos(entity player)
292 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
294 SORT_SWAP(player, ent);
296 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
298 SORT_SWAP(ent, player);
302 float Scoreboard_CompareTeamScores(entity left, entity right)
304 if(left.team == NUM_SPECTATOR)
306 if(right.team == NUM_SPECTATOR)
311 for(int i = -2; i < MAX_TEAMSCORE; ++i)
315 if (fld_idx == -1) fld_idx = ts_primary;
316 else if (ts_secondary == ts_primary) continue;
317 else fld_idx = ts_secondary;
322 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
325 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
326 if (r >= 0) return r;
329 if (left.team < right.team)
335 void Scoreboard_UpdateTeamPos(entity Team)
338 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
340 SORT_SWAP(Team, ent);
342 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
344 SORT_SWAP(ent, Team);
348 void Cmd_Scoreboard_Help()
350 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
351 LOG_HELP(_("Usage:"));
352 LOG_HELP("^2scoreboard_columns_set ^3default");
353 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
354 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
355 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
356 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
357 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
358 LOG_HELP(_("The following field names are recognized (case insensitive):"));
364 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
365 "of game types, then a slash, to make the field show up only in these\n"
366 "or in all but these game types. You can also specify 'all' as a\n"
367 "field to show all fields available for the current game mode."));
370 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
371 "include/exclude ALL teams/noteams game modes."));
374 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
375 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
376 "right of the vertical bar aligned to the right."));
377 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
378 "other gamemodes except DM."));
381 // NOTE: adding a gametype with ? to not warn for an optional field
382 // make sure it's excluded in a previous exclusive rule, if any
383 // otherwise the previous exclusive rule warns anyway
384 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
385 #define SCOREBOARD_DEFAULT_COLUMNS \
386 "ping pl fps name |" \
387 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
388 " -teams,lms/deaths +ft,tdm/deaths" \
390 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
391 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
392 " +tdm,ft,dom,ons,as/teamkills"\
393 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
394 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
395 " +lms/lives +lms/rank" \
396 " +kh/kckills +kh/losses +kh/caps" \
397 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
398 " +as/objectives +nb/faults +nb/goals" \
399 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
400 " +dom/ticks +dom/takes" \
401 " -lms,rc,cts,inv,nb/score"
403 void Cmd_Scoreboard_SetFields(int argc)
408 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
412 return; // do nothing, we don't know gametype and scores yet
414 // sbt_fields uses strunzone on the titles!
415 if(!sbt_field_title[0])
416 for(i = 0; i < MAX_SBT_FIELDS; ++i)
417 sbt_field_title[i] = strzone("(null)");
419 // TODO: re enable with gametype dependant cvars?
420 if(argc < 3) // no arguments provided
421 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
424 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
428 if(argv(2) == "default" || argv(2) == "expand_default")
430 if(argv(2) == "expand_default")
431 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
432 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
434 else if(argv(2) == "all" || argv(2) == "ALL")
436 string s = "ping pl name |"; // scores without label (not really scores)
439 // scores without label
440 s = strcat(s, " ", "sum");
441 s = strcat(s, " ", "kdratio");
442 s = strcat(s, " ", "frags");
444 FOREACH(Scores, true, {
446 if(it != ps_secondary)
447 if(scores_label(it) != "")
448 s = strcat(s, " ", scores_label(it));
450 if(ps_secondary != ps_primary)
451 s = strcat(s, " ", scores_label(ps_secondary));
452 s = strcat(s, " ", scores_label(ps_primary));
453 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
460 hud_fontsize = HUD_GetFontsize("hud_fontsize");
462 for(i = 1; i < argc - 1; ++i)
465 bool nocomplain = false;
466 if(substring(str, 0, 1) == "?")
469 str = substring(str, 1, strlen(str) - 1);
472 slash = strstrofs(str, "/", 0);
475 pattern = substring(str, 0, slash);
476 str = substring(str, slash + 1, strlen(str) - (slash + 1));
478 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
482 str = strtolower(str);
483 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
484 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
489 // fields without a label (not networked via the score system)
490 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
491 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
492 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
493 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
494 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
495 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
496 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
497 default: // fields with a label
499 // map alternative labels
500 if (str == "damage") str = "dmg";
501 if (str == "damagetaken") str = "dmgtaken";
503 FOREACH(Scores, true, {
504 if (str == strtolower(scores_label(it))) {
506 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
510 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
511 if(!nocomplain && str != "fps") // server can disable the fps field
512 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
514 strfree(sbt_field_title[sbt_num_fields]);
515 sbt_field_size[sbt_num_fields] = 0;
519 sbt_field[sbt_num_fields] = j;
522 if(j == ps_secondary)
523 have_secondary = true;
528 if(sbt_num_fields >= MAX_SBT_FIELDS)
532 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
534 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
535 have_secondary = true;
536 if(ps_primary == ps_secondary)
537 have_secondary = true;
538 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
540 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
544 strfree(sbt_field_title[sbt_num_fields]);
545 for(i = sbt_num_fields; i > 0; --i)
547 sbt_field_title[i] = sbt_field_title[i-1];
548 sbt_field_size[i] = sbt_field_size[i-1];
549 sbt_field[i] = sbt_field[i-1];
551 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
552 sbt_field[0] = SP_NAME;
554 LOG_INFO("fixed missing field 'name'");
558 strfree(sbt_field_title[sbt_num_fields]);
559 for(i = sbt_num_fields; i > 1; --i)
561 sbt_field_title[i] = sbt_field_title[i-1];
562 sbt_field_size[i] = sbt_field_size[i-1];
563 sbt_field[i] = sbt_field[i-1];
565 sbt_field_title[1] = strzone("|");
566 sbt_field[1] = SP_SEPARATOR;
567 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
569 LOG_INFO("fixed missing field '|'");
572 else if(!have_separator)
574 strcpy(sbt_field_title[sbt_num_fields], "|");
575 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
576 sbt_field[sbt_num_fields] = SP_SEPARATOR;
578 LOG_INFO("fixed missing field '|'");
582 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
583 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
584 sbt_field[sbt_num_fields] = ps_secondary;
586 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
590 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
591 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
592 sbt_field[sbt_num_fields] = ps_primary;
594 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
598 sbt_field[sbt_num_fields] = SP_END;
601 string Scoreboard_AddPlayerId(string pl_name, entity pl)
603 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
604 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
605 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
609 vector sbt_field_rgb;
610 string sbt_field_icon0;
611 string sbt_field_icon1;
612 string sbt_field_icon2;
613 vector sbt_field_icon0_rgb;
614 vector sbt_field_icon1_rgb;
615 vector sbt_field_icon2_rgb;
616 string Scoreboard_GetName(entity pl)
618 if(ready_waiting && pl.ready)
620 sbt_field_icon0 = "gfx/scoreboard/player_ready";
624 int f = entcs_GetClientColors(pl.sv_entnum);
626 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
627 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
628 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
629 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
630 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
633 return entcs_GetName(pl.sv_entnum);
636 string Scoreboard_GetField(entity pl, PlayerScoreField field)
638 float tmp, num, denom;
641 sbt_field_rgb = '1 1 1';
642 sbt_field_icon0 = "";
643 sbt_field_icon1 = "";
644 sbt_field_icon2 = "";
645 sbt_field_icon0_rgb = '1 1 1';
646 sbt_field_icon1_rgb = '1 1 1';
647 sbt_field_icon2_rgb = '1 1 1';
652 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
653 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
657 tmp = max(0, min(220, f-80)) / 220;
658 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
664 f = pl.ping_packetloss;
665 tmp = pl.ping_movementloss;
666 if(f == 0 && tmp == 0)
668 str = ftos(ceil(f * 100));
670 str = strcat(str, "~", ftos(ceil(tmp * 100)));
671 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
672 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
676 str = Scoreboard_GetName(pl);
677 if (autocvar_hud_panel_scoreboard_playerid)
678 str = Scoreboard_AddPlayerId(str, pl);
682 f = pl.(scores(SP_KILLS));
683 f -= pl.(scores(SP_SUICIDES));
687 num = pl.(scores(SP_KILLS));
688 denom = pl.(scores(SP_DEATHS));
691 sbt_field_rgb = '0 1 0';
692 str = sprintf("%d", num);
693 } else if(num <= 0) {
694 sbt_field_rgb = '1 0 0';
695 str = sprintf("%.1f", num/denom);
697 str = sprintf("%.1f", num/denom);
701 f = pl.(scores(SP_KILLS));
702 f -= pl.(scores(SP_DEATHS));
705 sbt_field_rgb = '0 1 0';
707 sbt_field_rgb = '1 1 1';
709 sbt_field_rgb = '1 0 0';
715 float elo = pl.(scores(SP_ELO));
717 case -1: return "...";
718 case -2: return _("N/A");
719 default: return ftos(elo);
725 float fps = pl.(scores(SP_FPS));
728 sbt_field_rgb = '1 1 1';
729 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
731 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
732 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
736 case SP_DMG: case SP_DMGTAKEN:
737 return sprintf("%.1f k", pl.(scores(field)) / 1000);
739 default: case SP_SCORE:
740 tmp = pl.(scores(field));
741 f = scores_flags(field);
742 if(field == ps_primary)
743 sbt_field_rgb = '1 1 0';
744 else if(field == ps_secondary)
745 sbt_field_rgb = '0 1 1';
747 sbt_field_rgb = '1 1 1';
748 return ScoreString(f, tmp);
753 float sbt_fixcolumnwidth_len;
754 float sbt_fixcolumnwidth_iconlen;
755 float sbt_fixcolumnwidth_marginlen;
757 string Scoreboard_FixColumnWidth(int i, string str)
763 sbt_fixcolumnwidth_iconlen = 0;
765 if(sbt_field_icon0 != "")
767 sz = draw_getimagesize(sbt_field_icon0);
769 if(sbt_fixcolumnwidth_iconlen < f)
770 sbt_fixcolumnwidth_iconlen = f;
773 if(sbt_field_icon1 != "")
775 sz = draw_getimagesize(sbt_field_icon1);
777 if(sbt_fixcolumnwidth_iconlen < f)
778 sbt_fixcolumnwidth_iconlen = f;
781 if(sbt_field_icon2 != "")
783 sz = draw_getimagesize(sbt_field_icon2);
785 if(sbt_fixcolumnwidth_iconlen < f)
786 sbt_fixcolumnwidth_iconlen = f;
789 if(sbt_fixcolumnwidth_iconlen != 0)
791 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
792 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
795 sbt_fixcolumnwidth_marginlen = 0;
797 if(sbt_field[i] == SP_NAME) // name gets all remaining space
800 float remaining_space = 0;
801 for(j = 0; j < sbt_num_fields; ++j)
803 if (sbt_field[i] != SP_SEPARATOR)
804 remaining_space += sbt_field_size[j] + hud_fontsize.x;
805 sbt_field_size[i] = panel_size.x - remaining_space;
807 if (sbt_fixcolumnwidth_iconlen != 0)
808 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
809 float namesize = panel_size.x - remaining_space;
810 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
811 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
813 max_namesize = vid_conwidth - remaining_space;
816 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
818 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
819 if(sbt_field_size[i] < f)
820 sbt_field_size[i] = f;
825 void Scoreboard_initFieldSizes()
827 for(int i = 0; i < sbt_num_fields; ++i)
829 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
830 Scoreboard_FixColumnWidth(i, "");
834 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
837 vector column_dim = eY * panel_size.y;
839 column_dim.y -= 1.25 * hud_fontsize.y;
840 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
841 pos.x += hud_fontsize.x * 0.5;
842 for(i = 0; i < sbt_num_fields; ++i)
844 if(sbt_field[i] == SP_SEPARATOR)
846 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
849 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
850 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
851 pos.x += column_dim.x;
853 if(sbt_field[i] == SP_SEPARATOR)
855 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
856 for(i = sbt_num_fields - 1; i > 0; --i)
858 if(sbt_field[i] == SP_SEPARATOR)
861 pos.x -= sbt_field_size[i];
866 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
867 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
870 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
871 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
872 pos.x -= hud_fontsize.x;
877 pos.y += 1.25 * hud_fontsize.y;
881 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
883 TC(bool, is_self); TC(int, pl_number);
885 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
887 vector h_pos = item_pos;
888 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
889 // alternated rows highlighting
891 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
892 else if((sbt_highlight) && (!(pl_number % 2)))
893 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
895 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
897 vector pos = item_pos;
898 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
900 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
902 pos.x += hud_fontsize.x * 0.5;
903 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
904 vector tmp = '0 0 0';
906 PlayerScoreField field;
907 for(i = 0; i < sbt_num_fields; ++i)
909 field = sbt_field[i];
910 if(field == SP_SEPARATOR)
913 if(is_spec && field != SP_NAME && field != SP_PING) {
914 pos.x += sbt_field_size[i] + hud_fontsize.x;
917 str = Scoreboard_GetField(pl, field);
918 str = Scoreboard_FixColumnWidth(i, str);
920 pos.x += sbt_field_size[i] + hud_fontsize.x;
922 if(field == SP_NAME) {
923 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
924 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
926 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
927 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
930 tmp.x = sbt_field_size[i] + hud_fontsize.x;
931 if(sbt_field_icon0 != "")
932 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
933 if(sbt_field_icon1 != "")
934 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
935 if(sbt_field_icon2 != "")
936 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 if(sbt_field[i] == SP_SEPARATOR)
941 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
942 for(i = sbt_num_fields-1; i > 0; --i)
944 field = sbt_field[i];
945 if(field == SP_SEPARATOR)
948 if(is_spec && field != SP_NAME && field != SP_PING) {
949 pos.x -= sbt_field_size[i] + hud_fontsize.x;
953 str = Scoreboard_GetField(pl, field);
954 str = Scoreboard_FixColumnWidth(i, str);
956 if(field == SP_NAME) {
957 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
958 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
960 tmp.x = sbt_fixcolumnwidth_len;
961 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
964 tmp.x = sbt_field_size[i];
965 if(sbt_field_icon0 != "")
966 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
967 if(sbt_field_icon1 != "")
968 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
969 if(sbt_field_icon2 != "")
970 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
971 pos.x -= sbt_field_size[i] + hud_fontsize.x;
976 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
979 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
982 vector h_pos = item_pos;
983 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
985 bool complete = (this_team == NUM_SPECTATOR);
988 if((sbt_highlight) && (!(pl_number % 2)))
989 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
991 vector pos = item_pos;
992 pos.x += hud_fontsize.x * 0.5;
993 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
995 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
997 width_limit -= stringwidth("...", false, hud_fontsize);
998 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
999 static float max_name_width = 0;
1001 float fieldsize = 0;
1002 float min_fieldsize = 0;
1003 float fieldpadding = hud_fontsize.x * 0.25;
1004 if(this_team == NUM_SPECTATOR)
1006 if(autocvar_hud_panel_scoreboard_spectators_showping)
1007 min_fieldsize = stringwidth("999", false, hud_fontsize);
1009 else if(autocvar_hud_panel_scoreboard_others_showscore)
1010 min_fieldsize = stringwidth("99", false, hud_fontsize);
1011 for(i = 0; pl; pl = pl.sort_next)
1013 if(pl.team != this_team)
1015 if(pl == ignored_pl)
1019 if(this_team == NUM_SPECTATOR)
1021 if(autocvar_hud_panel_scoreboard_spectators_showping)
1022 field = Scoreboard_GetField(pl, SP_PING);
1024 else if(autocvar_hud_panel_scoreboard_others_showscore)
1025 field = Scoreboard_GetField(pl, SP_SCORE);
1027 string str = entcs_GetName(pl.sv_entnum);
1028 if (autocvar_hud_panel_scoreboard_playerid)
1029 str = Scoreboard_AddPlayerId(str, pl);
1030 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1031 float column_width = stringwidth(str, true, hud_fontsize);
1032 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1034 if(column_width > max_name_width)
1035 max_name_width = column_width;
1036 column_width = max_name_width;
1040 fieldsize = stringwidth(field, false, hud_fontsize);
1041 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1044 if(pos.x + column_width > width_limit)
1049 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1054 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1055 pos.y += hud_fontsize.y * 1.25;
1059 vector name_pos = pos;
1060 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1061 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1062 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1065 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1066 h_size.y = hud_fontsize.y;
1067 vector field_pos = pos;
1068 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1069 field_pos.x += column_width - h_size.x;
1071 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1072 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1073 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1077 h_size.x = column_width + hud_fontsize.x * 0.25;
1078 h_size.y = hud_fontsize.y;
1079 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1081 pos.x += column_width;
1082 pos.x += hud_fontsize.x;
1084 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1087 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1089 int max_players = 999;
1090 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1092 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1095 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1096 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1097 height /= team_count;
1100 height -= panel_bg_padding * 2; // - padding
1101 max_players = floor(height / (hud_fontsize.y * 1.25));
1102 if(max_players <= 1)
1104 if(max_players == tm.team_size)
1109 entity me = playerslots[current_player];
1111 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1112 panel_size.y += panel_bg_padding * 2;
1115 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1116 if(panel.current_panel_bg != "0")
1117 end_pos.y += panel_bg_border * 2;
1119 if(panel_bg_padding)
1121 panel_pos += '1 1 0' * panel_bg_padding;
1122 panel_size -= '2 2 0' * panel_bg_padding;
1126 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1130 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1132 pos.y += 1.25 * hud_fontsize.y;
1135 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1137 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1140 // print header row and highlight columns
1141 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1143 // fill the table and draw the rows
1144 bool is_self = false;
1145 bool self_shown = false;
1147 for(pl = players.sort_next; pl; pl = pl.sort_next)
1149 if(pl.team != tm.team)
1151 if(i == max_players - 2 && pl != me)
1153 if(!self_shown && me.team == tm.team)
1155 Scoreboard_DrawItem(pos, rgb, me, true, i);
1157 pos.y += 1.25 * hud_fontsize.y;
1161 if(i >= max_players - 1)
1163 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1166 is_self = (pl.sv_entnum == current_player);
1167 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1170 pos.y += 1.25 * hud_fontsize.y;
1174 panel_size.x += panel_bg_padding * 2; // restore initial width
1178 bool Scoreboard_WouldDraw()
1180 if (MUTATOR_CALLHOOK(DrawScoreboard))
1182 else if (QuickMenu_IsOpened())
1184 else if (HUD_Radar_Clickable())
1186 else if (scoreboard_showscores)
1188 else if (intermission == 1)
1190 else if (intermission == 2)
1192 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1193 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1197 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1202 float average_accuracy;
1203 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1205 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1207 WepSet weapons_stat = WepSet_GetFromStat();
1208 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1209 int disownedcnt = 0;
1211 FOREACH(Weapons, it != WEP_Null, {
1212 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1214 WepSet set = it.m_wepset;
1215 if(it.spawnflags & WEP_TYPE_OTHER)
1220 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1222 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1229 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1230 if (weapon_cnt <= 0) return pos;
1233 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1235 int columns = ceil(weapon_cnt / rows);
1237 float aspect = max(0.001, autocvar_hud_panel_weapons_aspect);
1238 float weapon_height = hud_fontsize.y * 2.3 / aspect;
1239 float height = weapon_height + hud_fontsize.y;
1241 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);
1242 pos.y += 1.25 * hud_fontsize.y;
1243 if(panel.current_panel_bg != "0")
1244 pos.y += panel_bg_border;
1247 panel_size.y = height * rows;
1248 panel_size.y += panel_bg_padding * 2;
1250 float panel_bg_alpha_save = panel_bg_alpha;
1251 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1253 panel_bg_alpha = panel_bg_alpha_save;
1255 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1256 if(panel.current_panel_bg != "0")
1257 end_pos.y += panel_bg_border * 2;
1259 if(panel_bg_padding)
1261 panel_pos += '1 1 0' * panel_bg_padding;
1262 panel_size -= '2 2 0' * panel_bg_padding;
1266 vector tmp = panel_size;
1268 float weapon_width = tmp.x / columns / rows;
1271 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1275 // column highlighting
1276 for (int i = 0; i < columns; ++i)
1278 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);
1281 for (int i = 0; i < rows; ++i)
1282 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1285 average_accuracy = 0;
1286 int weapons_with_stats = 0;
1288 pos.x += weapon_width / 2;
1290 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1293 Accuracy_LoadColors();
1295 float oldposx = pos.x;
1299 FOREACH(Weapons, it != WEP_Null, {
1300 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1302 WepSet set = it.m_wepset;
1303 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1305 if (it.spawnflags & WEP_TYPE_OTHER)
1309 if (weapon_stats >= 0)
1310 weapon_alpha = sbt_fg_alpha;
1312 weapon_alpha = 0.2 * sbt_fg_alpha;
1315 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1317 if (weapon_stats >= 0) {
1318 weapons_with_stats += 1;
1319 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1321 string s = sprintf("%d%%", weapon_stats * 100);
1322 float padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2;
1324 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1325 rgb = Accuracy_GetColor(weapon_stats);
1327 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1329 tmpos.x += weapon_width * rows;
1330 pos.x += weapon_width * rows;
1331 if (rows == 2 && column == columns - 1) {
1339 if (weapons_with_stats)
1340 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1342 panel_size.x += panel_bg_padding * 2; // restore initial width
1347 bool is_item_filtered(entity it)
1349 if (!autocvar_hud_panel_scoreboard_itemstats_filter)
1351 int mask = autocvar_hud_panel_scoreboard_itemstats_filter_mask;
1354 if (it.instanceOfArmor || it.instanceOfHealth)
1356 int ha_mask = floor(mask) % 10;
1359 default: return false;
1360 case 4: if (it == ITEM_HealthMega || it == ITEM_ArmorMega) return true; // else fallthrough
1361 case 3: if (it == ITEM_HealthBig || it == ITEM_ArmorBig) return true; // else fallthrough
1362 case 2: if (it == ITEM_HealthMedium || it == ITEM_ArmorMedium) return true; // else fallthrough
1363 case 1: if (it == ITEM_HealthSmall || it == ITEM_ArmorSmall) return true; // else fallthrough
1366 if (it.instanceOfAmmo)
1368 int ammo_mask = floor(mask / 10) % 10;
1369 return (ammo_mask == 1);
1374 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1376 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1378 int disowned_cnt = 0;
1379 int uninteresting_cnt = 0;
1380 IL_EACH(default_order_items, true, {
1381 int q = g_inventory.inv_items[it.m_id];
1382 //q = 1; // debug: display all items
1383 if (is_item_filtered(it))
1384 ++uninteresting_cnt;
1388 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1389 int n = items_cnt - disowned_cnt;
1390 if (n <= 0) return pos;
1392 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1393 int columns = max(6, ceil(n / rows));
1395 float item_height = hud_fontsize.y * 2.3;
1396 float height = item_height + hud_fontsize.y;
1398 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1399 pos.y += 1.25 * hud_fontsize.y;
1400 if(panel.current_panel_bg != "0")
1401 pos.y += panel_bg_border;
1404 panel_size.y = height * rows;
1405 panel_size.y += panel_bg_padding * 2;
1407 float panel_bg_alpha_save = panel_bg_alpha;
1408 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1410 panel_bg_alpha = panel_bg_alpha_save;
1412 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1413 if(panel.current_panel_bg != "0")
1414 end_pos.y += panel_bg_border * 2;
1416 if(panel_bg_padding)
1418 panel_pos += '1 1 0' * panel_bg_padding;
1419 panel_size -= '2 2 0' * panel_bg_padding;
1423 vector tmp = panel_size;
1425 float item_width = tmp.x / columns / rows;
1428 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1432 // column highlighting
1433 for (int i = 0; i < columns; ++i)
1435 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);
1438 for (int i = 0; i < rows; ++i)
1439 drawfill(pos + eY * (item_height + height * i), vec2(panel_size.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1443 pos.x += item_width / 2;
1445 float oldposx = pos.x;
1449 IL_EACH(default_order_items, !is_item_filtered(it), {
1450 int n = g_inventory.inv_items[it.m_id];
1451 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1452 if (n <= 0) continue;
1453 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);
1455 float padding = (item_width - stringwidth(s, false, hud_fontsize)) / 2;
1456 drawstring(tmpos + vec2(padding, item_height), s, hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1457 tmpos.x += item_width * rows;
1458 pos.x += item_width * rows;
1459 if (rows == 2 && column == columns - 1) {
1467 panel_size.x += panel_bg_padding * 2; // restore initial width
1472 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1474 pos.x += hud_fontsize.x * 0.25;
1475 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1476 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1477 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1479 pos.y += hud_fontsize.y;
1484 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1485 float stat_secrets_found, stat_secrets_total;
1486 float stat_monsters_killed, stat_monsters_total;
1490 // get monster stats
1491 stat_monsters_killed = STAT(MONSTERS_KILLED);
1492 stat_monsters_total = STAT(MONSTERS_TOTAL);
1494 // get secrets stats
1495 stat_secrets_found = STAT(SECRETS_FOUND);
1496 stat_secrets_total = STAT(SECRETS_TOTAL);
1498 // get number of rows
1499 if(stat_secrets_total)
1501 if(stat_monsters_total)
1504 // if no rows, return
1508 // draw table header
1509 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1510 pos.y += 1.25 * hud_fontsize.y;
1511 if(panel.current_panel_bg != "0")
1512 pos.y += panel_bg_border;
1515 panel_size.y = hud_fontsize.y * rows;
1516 panel_size.y += panel_bg_padding * 2;
1519 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1520 if(panel.current_panel_bg != "0")
1521 end_pos.y += panel_bg_border * 2;
1523 if(panel_bg_padding)
1525 panel_pos += '1 1 0' * panel_bg_padding;
1526 panel_size -= '2 2 0' * panel_bg_padding;
1530 vector tmp = panel_size;
1533 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1536 if(stat_monsters_total)
1538 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1539 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1543 if(stat_secrets_total)
1545 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1546 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1549 panel_size.x += panel_bg_padding * 2; // restore initial width
1553 int rankings_rows = 0;
1554 int rankings_columns = 0;
1555 int rankings_cnt = 0;
1556 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1559 RANKINGS_RECEIVED_CNT = 0;
1560 for (i=RANKINGS_CNT-1; i>=0; --i)
1562 ++RANKINGS_RECEIVED_CNT;
1564 if (RANKINGS_RECEIVED_CNT == 0)
1567 vector hl_rgb = rgb + '0.5 0.5 0.5';
1569 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1570 pos.y += 1.25 * hud_fontsize.y;
1571 if(panel.current_panel_bg != "0")
1572 pos.y += panel_bg_border;
1577 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1579 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1584 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1586 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1590 float ranksize = 3 * hud_fontsize.x;
1591 float timesize = 5 * hud_fontsize.x;
1592 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1593 rankings_columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1594 rankings_columns = min(rankings_columns, RANKINGS_RECEIVED_CNT);
1597 rankings_cnt = RANKINGS_RECEIVED_CNT;
1598 rankings_rows = ceil(rankings_cnt / rankings_columns);
1601 // expand name column to fill the entire row
1602 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * rankings_columns) / rankings_columns;
1603 namesize += available_space;
1604 columnsize.x += available_space;
1606 panel_size.y = rankings_rows * 1.25 * hud_fontsize.y;
1607 panel_size.y += panel_bg_padding * 2;
1611 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1612 if(panel.current_panel_bg != "0")
1613 end_pos.y += panel_bg_border * 2;
1615 if(panel_bg_padding)
1617 panel_pos += '1 1 0' * panel_bg_padding;
1618 panel_size -= '2 2 0' * panel_bg_padding;
1624 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1626 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1628 int column = 0, j = 0;
1629 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1630 for(i = 0; i < rankings_cnt; ++i)
1637 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1638 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1639 else if(!((j + column) & 1) && sbt_highlight)
1640 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1642 str = count_ordinal(i+1);
1643 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1644 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1645 str = ColorTranslateRGB(grecordholder[i]);
1647 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1648 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1650 pos.y += 1.25 * hud_fontsize.y;
1652 if(j >= rankings_rows)
1656 pos.x += panel_size.x / rankings_columns;
1657 pos.y = panel_pos.y;
1660 strfree(zoned_name_self);
1662 panel_size.x += panel_bg_padding * 2; // restore initial width
1666 float scoreboard_time;
1667 bool have_weapon_stats;
1668 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1670 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1672 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1675 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1676 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1682 if (!have_weapon_stats)
1684 FOREACH(Weapons, it != WEP_Null, {
1685 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1686 if (weapon_stats >= 0)
1688 have_weapon_stats = true;
1692 if (!have_weapon_stats)
1699 bool have_item_stats;
1700 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1702 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1704 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1707 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1708 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1714 if (!have_item_stats)
1716 IL_EACH(default_order_items, true, {
1717 if (!is_item_filtered(it))
1719 int q = g_inventory.inv_items[it.m_id];
1720 //q = 1; // debug: display all items
1723 have_item_stats = true;
1728 if (!have_item_stats)
1735 vector Scoreboard_Spectators_Draw(vector pos) {
1740 for(pl = players.sort_next; pl; pl = pl.sort_next)
1742 if(pl.team == NUM_SPECTATOR)
1744 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1745 if(tm.team == NUM_SPECTATOR)
1747 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1748 draw_beginBoldFont();
1749 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1751 pos.y += 1.25 * hud_fontsize.y;
1753 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1754 pos.y += 1.25 * hud_fontsize.y;
1759 if (str != "") // if there's at least one spectator
1760 pos.y += 0.5 * hud_fontsize.y;
1765 void Scoreboard_Draw()
1767 if(!autocvar__hud_configure)
1769 if(!hud_draw_maximized) return;
1771 // frametime checks allow to toggle the scoreboard even when the game is paused
1772 if(scoreboard_active) {
1773 if (scoreboard_fade_alpha == 0)
1774 scoreboard_time = time;
1775 if(hud_configure_menu_open == 1)
1776 scoreboard_fade_alpha = 1;
1777 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1778 if (scoreboard_fadeinspeed && frametime)
1779 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1781 scoreboard_fade_alpha = 1;
1782 if(hud_fontsize_str != autocvar_hud_fontsize)
1784 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1785 Scoreboard_initFieldSizes();
1786 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1790 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1791 if (scoreboard_fadeoutspeed && frametime)
1792 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1794 scoreboard_fade_alpha = 0;
1797 if (!scoreboard_fade_alpha)
1799 scoreboard_acc_fade_alpha = 0;
1800 scoreboard_itemstats_fade_alpha = 0;
1805 scoreboard_fade_alpha = 0;
1807 if (autocvar_hud_panel_scoreboard_dynamichud)
1810 HUD_Scale_Disable();
1812 if(scoreboard_fade_alpha <= 0)
1814 panel_fade_alpha *= scoreboard_fade_alpha;
1815 HUD_Panel_LoadCvars();
1817 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1818 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1819 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1820 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1821 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1822 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1823 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1825 // don't overlap with con_notify
1826 if(!autocvar__hud_configure)
1827 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1829 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1830 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1831 scoreboard_left = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1832 scoreboard_right = scoreboard_left + fixed_scoreboard_width;
1833 panel_pos.x = scoreboard_left;
1834 panel_size.x = fixed_scoreboard_width;
1836 Scoreboard_UpdatePlayerTeams();
1838 scoreboard_top = panel_pos.y;
1839 vector pos = panel_pos;
1844 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1846 // Begin of Game Info Section
1847 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1848 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1850 // Game Info: Game Type
1851 str = MapInfo_Type_ToText(gametype);
1852 draw_beginBoldFont();
1853 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);
1856 // Game Info: Game Detail
1857 float tl = STAT(TIMELIMIT);
1858 float fl = STAT(FRAGLIMIT);
1859 float ll = STAT(LEADLIMIT);
1860 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1863 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1864 if(!gametype.m_hidelimits)
1869 str = strcat(str, "^7 / "); // delimiter
1872 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1873 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1874 (teamscores_label(ts_primary) == "fastest") ? "" :
1875 TranslateScoresLabel(teamscores_label(ts_primary))));
1879 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1880 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1881 (scores_label(ps_primary) == "fastest") ? "" :
1882 TranslateScoresLabel(scores_label(ps_primary))));
1887 if(tl > 0 || fl > 0)
1890 if (ll_and_fl && fl > 0)
1891 str = strcat(str, "^7 & ");
1893 str = strcat(str, "^7 / ");
1898 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1899 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1900 (teamscores_label(ts_primary) == "fastest") ? "" :
1901 TranslateScoresLabel(teamscores_label(ts_primary))));
1905 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1906 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1907 (scores_label(ps_primary) == "fastest") ? "" :
1908 TranslateScoresLabel(scores_label(ps_primary))));
1913 pos.y += sb_gameinfo_type_fontsize.y;
1914 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
1916 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1917 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1918 // End of Game Info Section
1920 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1921 if(panel.current_panel_bg != "0")
1922 pos.y += panel_bg_border;
1924 // Draw the scoreboard
1925 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1928 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1932 vector panel_bg_color_save = panel_bg_color;
1933 vector team_score_baseoffset;
1934 vector team_size_baseoffset;
1935 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1937 // put team score to the left of scoreboard (and team size to the right)
1938 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1939 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1940 if(panel.current_panel_bg != "0")
1942 team_score_baseoffset.x -= panel_bg_border;
1943 team_size_baseoffset.x += panel_bg_border;
1948 // put team score to the right of scoreboard (and team size to the left)
1949 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1950 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1951 if(panel.current_panel_bg != "0")
1953 team_score_baseoffset.x += panel_bg_border;
1954 team_size_baseoffset.x -= panel_bg_border;
1958 int team_size_total = 0;
1959 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1961 // calculate team size total (sum of all team sizes)
1962 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1963 if(tm.team != NUM_SPECTATOR)
1964 team_size_total += tm.team_size;
1967 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1969 if(tm.team == NUM_SPECTATOR)
1974 draw_beginBoldFont();
1975 vector rgb = Team_ColorRGB(tm.team);
1976 str = ftos(tm.(teamscores(ts_primary)));
1977 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1979 // team score on the left (default)
1980 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1984 // team score on the right
1985 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1987 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1989 // team size (if set to show on the side)
1990 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1992 // calculate the starting position for the whole team size info string
1993 str = sprintf("%d/%d", tm.team_size, team_size_total);
1994 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1996 // team size on the left
1997 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2001 // team size on the right
2002 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2004 str = sprintf("%d", tm.team_size);
2005 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2006 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2007 str = sprintf("/%d", team_size_total);
2008 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2012 // secondary score, e.g. keyhunt
2013 if(ts_primary != ts_secondary)
2015 str = ftos(tm.(teamscores(ts_secondary)));
2016 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2019 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2024 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2027 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2030 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2031 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2032 else if(panel_bg_color_team > 0)
2033 panel_bg_color = rgb * panel_bg_color_team;
2035 panel_bg_color = rgb;
2036 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2038 panel_bg_color = panel_bg_color_save;
2042 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2043 if(tm.team != NUM_SPECTATOR)
2046 // display it anyway
2047 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2050 // draw scoreboard spectators before accuracy and item stats
2051 if (autocvar_hud_panel_scoreboard_spectators_position == 0) {
2052 pos = Scoreboard_Spectators_Draw(pos);
2055 // draw accuracy and item stats
2056 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2057 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2058 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2059 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2061 // draw scoreboard spectators after accuracy and item stats and before rankings
2062 if (autocvar_hud_panel_scoreboard_spectators_position == 1) {
2063 pos = Scoreboard_Spectators_Draw(pos);
2066 if(MUTATOR_CALLHOOK(ShowRankings)) {
2067 string ranktitle = M_ARGV(0, string);
2068 string unit = GetSpeedUnit(autocvar_hud_panel_physics_speed_unit);
2069 if(race_speedaward) {
2070 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2071 pos.y += 1.25 * hud_fontsize.y;
2073 if(race_speedaward_alltimebest) {
2074 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);
2075 pos.y += 1.25 * hud_fontsize.y;
2077 if (race_speedaward || race_speedaward_alltimebest)
2078 pos.y += 0.25 * hud_fontsize.y;
2079 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2084 // draw scoreboard spectators after rankings
2085 if (autocvar_hud_panel_scoreboard_spectators_position == 2) {
2086 pos = Scoreboard_Spectators_Draw(pos);
2089 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2091 // draw scoreboard spectators after mapstats
2092 if (autocvar_hud_panel_scoreboard_spectators_position == 3) {
2093 pos = Scoreboard_Spectators_Draw(pos);
2097 // print information about respawn status
2098 float respawn_time = STAT(RESPAWN_TIME);
2102 if(respawn_time < 0)
2104 // a negative number means we are awaiting respawn, time value is still the same
2105 respawn_time *= -1; // remove mark now that we checked it
2107 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2108 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2110 str = sprintf(_("^1Respawning in ^3%s^1..."),
2111 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2112 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2114 count_seconds(ceil(respawn_time - time))
2118 else if(time < respawn_time)
2120 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2121 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2122 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2124 count_seconds(ceil(respawn_time - time))
2128 else if(time >= respawn_time)
2129 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2131 pos.y += 1.2 * hud_fontsize.y;
2132 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2135 pos.y += hud_fontsize.y;
2136 if (scoreboard_fade_alpha < 1)
2137 scoreboard_bottom = scoreboard_top + (pos.y - scoreboard_top) * scoreboard_fade_alpha;
2138 else if (pos.y != scoreboard_bottom)
2140 if (pos.y > scoreboard_bottom)
2141 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - scoreboard_top));
2143 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - scoreboard_top));
2148 if (scoreboard_fade_alpha == 1)
2150 if (scoreboard_bottom > 0.95 * vid_conheight)
2151 rankings_rows = max(1, rankings_rows - 1);
2152 else if (scoreboard_bottom + 1.25 * hud_fontsize.y < 0.95 * vid_conheight)
2153 rankings_rows = min(ceil(RANKINGS_RECEIVED_CNT / rankings_columns), rankings_rows + 1);
2155 rankings_cnt = rankings_rows * rankings_columns;