1 #include "scoreboard.qh"
3 #include <client/draw.qh>
4 #include <client/hud/panel/chat.qh>
5 #include <client/hud/panel/quickmenu.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include <client/hud/panel/weapons.qh>
8 #include <common/constants.qh>
9 #include <common/ent_cs.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/net_linked.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
16 #include <common/items/inventory.qh>
20 void Scoreboard_Draw_Export(int fh)
22 // allow saving cvars that aesthetically change the panel into hud skin files
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
25 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
32 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
33 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_eliminated");
34 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
36 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
39 const int MAX_SBT_FIELDS = MAX_SCORE;
41 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
42 float sbt_field_size[MAX_SBT_FIELDS + 1];
43 string sbt_field_title[MAX_SBT_FIELDS + 1];
46 string autocvar_hud_fontsize;
47 string hud_fontsize_str;
52 float sbt_fg_alpha_self;
54 float sbt_highlight_alpha;
55 float sbt_highlight_alpha_self;
56 float sbt_highlight_alpha_eliminated;
58 // provide basic panel cvars to old clients
59 // TODO remove them after a future release (0.8.2+)
60 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
61 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
62 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
63 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
64 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
65 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
66 noref string autocvar_hud_panel_scoreboard_bg_border = "";
67 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
69 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
70 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
71 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
72 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
73 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
74 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
76 bool autocvar_hud_panel_scoreboard_table_highlight = true;
77 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated = 0.6;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_itemstats = true;
91 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
92 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
93 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
94 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
96 bool autocvar_hud_panel_scoreboard_dynamichud = false;
98 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
99 bool autocvar_hud_panel_scoreboard_others_showscore = true;
100 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
101 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
102 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 bool autocvar_hud_panel_scoreboard_playerid = false;
104 string autocvar_hud_panel_scoreboard_playerid_prefix = "#";
105 string autocvar_hud_panel_scoreboard_playerid_suffix = " ";
107 // mode 0: returns translated label
108 // mode 1: prints name and description of all the labels
109 string Label_getInfo(string label, int mode)
112 label = "bckills"; // first case in the switch
116 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
117 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
118 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")));
119 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
120 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
121 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
122 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
123 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
124 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
125 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
126 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
127 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
128 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
129 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
130 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
131 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
132 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
133 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
134 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
135 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
136 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
137 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
138 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
139 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
140 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
141 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
142 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
143 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")));
144 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
145 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
146 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
147 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
148 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
149 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
150 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
151 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
152 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
153 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
154 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
155 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
156 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
157 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
158 default: return label;
163 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
164 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
166 #define SB_EXTRA_SORTING_FIELDS 5
167 PlayerScoreField sb_extra_sorting_field[SB_EXTRA_SORTING_FIELDS];
168 void Scoreboard_InitScores()
172 ps_primary = ps_secondary = NULL;
173 ts_primary = ts_secondary = -1;
174 FOREACH(Scores, true, {
175 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
180 if(ps_primary == it || ps_secondary == it)
182 if (scores_label(it) == "kills") sb_extra_sorting_field[0] = it;
183 if (scores_label(it) == "deaths") sb_extra_sorting_field[1] = it;
184 if (scores_label(it) == "suicides") sb_extra_sorting_field[2] = it;
185 if (scores_label(it) == "dmg") sb_extra_sorting_field[3] = it;
186 if (scores_label(it) == "dmgtaken") sb_extra_sorting_field[4] = it;
188 if(ps_secondary == NULL)
189 ps_secondary = ps_primary;
191 for(i = 0; i < MAX_TEAMSCORE; ++i)
193 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
194 if(f == SFL_SORT_PRIO_PRIMARY)
196 if(f == SFL_SORT_PRIO_SECONDARY)
199 if(ts_secondary == -1)
200 ts_secondary = ts_primary;
202 Cmd_Scoreboard_SetFields(0);
206 void Scoreboard_UpdatePlayerTeams()
210 for(pl = players.sort_next; pl; pl = pl.sort_next)
213 int Team = entcs_GetScoreTeam(pl.sv_entnum);
214 if(SetTeam(pl, Team))
217 Scoreboard_UpdatePlayerPos(pl);
221 pl = players.sort_next;
226 print(strcat("PNUM: ", ftos(num), "\n"));
231 int Scoreboard_CompareScore(int vl, int vr, int f)
233 TC(int, vl); TC(int, vr); TC(int, f);
234 if(f & SFL_ZERO_IS_WORST)
236 if(vl == 0 && vr != 0)
238 if(vl != 0 && vr == 0)
242 return IS_INCREASING(f);
244 return IS_DECREASING(f);
248 float Scoreboard_ComparePlayerScores(entity left, entity right)
250 int vl = (left.gotscores) ? entcs_GetTeam(left.sv_entnum) : NUM_SPECTATOR;
251 int vr = (right.gotscores) ? entcs_GetTeam(right.sv_entnum) : NUM_SPECTATOR;
258 if(vl == NUM_SPECTATOR)
260 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
262 if(!left.gotscores && right.gotscores)
269 for (int i = -2; i < SB_EXTRA_SORTING_FIELDS; ++i)
273 if (!fld) fld = ps_primary;
274 else if (ps_secondary == ps_primary) continue;
275 else fld = ps_secondary;
279 fld = sb_extra_sorting_field[i];
280 if (fld == ps_primary || fld == ps_secondary) continue;
284 r = Scoreboard_CompareScore(left.scores(fld), right.scores(fld), scores_flags(fld));
285 if (r >= 0) return r;
288 if (left.sv_entnum < right.sv_entnum)
294 void Scoreboard_UpdatePlayerPos(entity player)
297 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
299 SORT_SWAP(player, ent);
301 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
303 SORT_SWAP(ent, player);
307 float Scoreboard_CompareTeamScores(entity left, entity right)
309 if(left.team == NUM_SPECTATOR)
311 if(right.team == NUM_SPECTATOR)
316 for(int i = -2; i < MAX_TEAMSCORE; ++i)
320 if (fld_idx == -1) fld_idx = ts_primary;
321 else if (ts_secondary == ts_primary) continue;
322 else fld_idx = ts_secondary;
327 if (fld_idx == ts_primary || fld_idx == ts_secondary) continue;
330 r = Scoreboard_CompareScore(left.teamscores(fld_idx), right.teamscores(fld_idx), teamscores_flags(fld_idx));
331 if (r >= 0) return r;
334 if (left.team < right.team)
340 void Scoreboard_UpdateTeamPos(entity Team)
343 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
345 SORT_SWAP(Team, ent);
347 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
349 SORT_SWAP(ent, Team);
353 void Cmd_Scoreboard_Help()
355 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
356 LOG_HELP(_("Usage:"));
357 LOG_HELP("^2scoreboard_columns_set ^3default");
358 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
359 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
360 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
361 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
362 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
363 LOG_HELP(_("The following field names are recognized (case insensitive):"));
369 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
370 "of game types, then a slash, to make the field show up only in these\n"
371 "or in all but these game types. You can also specify 'all' as a\n"
372 "field to show all fields available for the current game mode."));
375 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
376 "include/exclude ALL teams/noteams game modes."));
379 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
380 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
381 "right of the vertical bar aligned to the right."));
382 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
383 "other gamemodes except DM."));
386 // NOTE: adding a gametype with ? to not warn for an optional field
387 // make sure it's excluded in a previous exclusive rule, if any
388 // otherwise the previous exclusive rule warns anyway
389 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
390 #define SCOREBOARD_DEFAULT_COLUMNS \
391 "ping pl fps name |" \
392 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
393 " -teams,lms/deaths +ft,tdm/deaths" \
395 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
396 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
397 " +tdm,ft,dom,ons,as/teamkills"\
398 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
399 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
400 " +lms/lives +lms/rank" \
401 " +kh/kckills +kh/losses +kh/caps" \
402 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
403 " +as/objectives +nb/faults +nb/goals" \
404 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
405 " +dom/ticks +dom/takes" \
406 " -lms,rc,cts,inv,nb/score"
408 void Cmd_Scoreboard_SetFields(int argc)
413 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
417 return; // do nothing, we don't know gametype and scores yet
419 // sbt_fields uses strunzone on the titles!
420 if(!sbt_field_title[0])
421 for(i = 0; i < MAX_SBT_FIELDS; ++i)
422 sbt_field_title[i] = strzone("(null)");
424 // TODO: re enable with gametype dependant cvars?
425 if(argc < 3) // no arguments provided
426 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
429 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
433 if(argv(2) == "default" || argv(2) == "expand_default")
435 if(argv(2) == "expand_default")
436 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
437 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
439 else if(argv(2) == "all")
441 string s = "ping pl name |"; // scores without a label
442 FOREACH(Scores, true, {
444 if(it != ps_secondary)
445 if(scores_label(it) != "")
446 s = strcat(s, " ", scores_label(it));
448 if(ps_secondary != ps_primary)
449 s = strcat(s, " ", scores_label(ps_secondary));
450 s = strcat(s, " ", scores_label(ps_primary));
451 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
458 hud_fontsize = HUD_GetFontsize("hud_fontsize");
460 for(i = 1; i < argc - 1; ++i)
463 bool nocomplain = false;
464 if(substring(str, 0, 1) == "?")
467 str = substring(str, 1, strlen(str) - 1);
470 slash = strstrofs(str, "/", 0);
473 pattern = substring(str, 0, slash);
474 str = substring(str, slash + 1, strlen(str) - (slash + 1));
476 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
480 str = strtolower(str);
481 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
482 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
487 // fields without a label (not networked via the score system)
488 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
489 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
490 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
491 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
492 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
493 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
494 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
495 default: // fields with a label
497 // map alternative labels
498 if (str == "damage") str = "dmg";
499 if (str == "damagetaken") str = "dmgtaken";
501 FOREACH(Scores, true, {
502 if (str == strtolower(scores_label(it))) {
504 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
508 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
509 if(!nocomplain && str != "fps") // server can disable the fps field
510 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
512 strfree(sbt_field_title[sbt_num_fields]);
513 sbt_field_size[sbt_num_fields] = 0;
517 sbt_field[sbt_num_fields] = j;
520 if(j == ps_secondary)
521 have_secondary = true;
526 if(sbt_num_fields >= MAX_SBT_FIELDS)
530 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
532 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
533 have_secondary = true;
534 if(ps_primary == ps_secondary)
535 have_secondary = true;
536 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
538 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
542 strfree(sbt_field_title[sbt_num_fields]);
543 for(i = sbt_num_fields; i > 0; --i)
545 sbt_field_title[i] = sbt_field_title[i-1];
546 sbt_field_size[i] = sbt_field_size[i-1];
547 sbt_field[i] = sbt_field[i-1];
549 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
550 sbt_field[0] = SP_NAME;
552 LOG_INFO("fixed missing field 'name'");
556 strfree(sbt_field_title[sbt_num_fields]);
557 for(i = sbt_num_fields; i > 1; --i)
559 sbt_field_title[i] = sbt_field_title[i-1];
560 sbt_field_size[i] = sbt_field_size[i-1];
561 sbt_field[i] = sbt_field[i-1];
563 sbt_field_title[1] = strzone("|");
564 sbt_field[1] = SP_SEPARATOR;
565 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
567 LOG_INFO("fixed missing field '|'");
570 else if(!have_separator)
572 strcpy(sbt_field_title[sbt_num_fields], "|");
573 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
574 sbt_field[sbt_num_fields] = SP_SEPARATOR;
576 LOG_INFO("fixed missing field '|'");
580 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
581 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
582 sbt_field[sbt_num_fields] = ps_secondary;
584 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
588 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
589 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
590 sbt_field[sbt_num_fields] = ps_primary;
592 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
596 sbt_field[sbt_num_fields] = SP_END;
599 string Scoreboard_AddPlayerId(string pl_name, entity pl)
601 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
602 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
603 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
607 vector sbt_field_rgb;
608 string sbt_field_icon0;
609 string sbt_field_icon1;
610 string sbt_field_icon2;
611 vector sbt_field_icon0_rgb;
612 vector sbt_field_icon1_rgb;
613 vector sbt_field_icon2_rgb;
614 string Scoreboard_GetName(entity pl)
616 if(ready_waiting && pl.ready)
618 sbt_field_icon0 = "gfx/scoreboard/player_ready";
622 int f = entcs_GetClientColors(pl.sv_entnum);
624 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
625 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
626 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
627 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
628 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
631 return entcs_GetName(pl.sv_entnum);
634 string Scoreboard_GetField(entity pl, PlayerScoreField field)
636 float tmp, num, denom;
639 sbt_field_rgb = '1 1 1';
640 sbt_field_icon0 = "";
641 sbt_field_icon1 = "";
642 sbt_field_icon2 = "";
643 sbt_field_icon0_rgb = '1 1 1';
644 sbt_field_icon1_rgb = '1 1 1';
645 sbt_field_icon2_rgb = '1 1 1';
650 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
651 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
655 tmp = max(0, min(220, f-80)) / 220;
656 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
662 f = pl.ping_packetloss;
663 tmp = pl.ping_movementloss;
664 if(f == 0 && tmp == 0)
666 str = ftos(ceil(f * 100));
668 str = strcat(str, "~", ftos(ceil(tmp * 100)));
669 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
670 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
674 str = Scoreboard_GetName(pl);
675 if (autocvar_hud_panel_scoreboard_playerid)
676 str = Scoreboard_AddPlayerId(str, pl);
680 f = pl.(scores(SP_KILLS));
681 f -= pl.(scores(SP_SUICIDES));
685 num = pl.(scores(SP_KILLS));
686 denom = pl.(scores(SP_DEATHS));
689 sbt_field_rgb = '0 1 0';
690 str = sprintf("%d", num);
691 } else if(num <= 0) {
692 sbt_field_rgb = '1 0 0';
693 str = sprintf("%.1f", num/denom);
695 str = sprintf("%.1f", num/denom);
699 f = pl.(scores(SP_KILLS));
700 f -= pl.(scores(SP_DEATHS));
703 sbt_field_rgb = '0 1 0';
705 sbt_field_rgb = '1 1 1';
707 sbt_field_rgb = '1 0 0';
713 float elo = pl.(scores(SP_ELO));
715 case -1: return "...";
716 case -2: return _("N/A");
717 default: return ftos(elo);
723 float fps = pl.(scores(SP_FPS));
726 sbt_field_rgb = '1 1 1';
727 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
729 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
730 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
734 case SP_DMG: case SP_DMGTAKEN:
735 return sprintf("%.1f k", pl.(scores(field)) / 1000);
737 default: case SP_SCORE:
738 tmp = pl.(scores(field));
739 f = scores_flags(field);
740 if(field == ps_primary)
741 sbt_field_rgb = '1 1 0';
742 else if(field == ps_secondary)
743 sbt_field_rgb = '0 1 1';
745 sbt_field_rgb = '1 1 1';
746 return ScoreString(f, tmp);
751 float sbt_fixcolumnwidth_len;
752 float sbt_fixcolumnwidth_iconlen;
753 float sbt_fixcolumnwidth_marginlen;
755 string Scoreboard_FixColumnWidth(int i, string str)
761 sbt_fixcolumnwidth_iconlen = 0;
763 if(sbt_field_icon0 != "")
765 sz = draw_getimagesize(sbt_field_icon0);
767 if(sbt_fixcolumnwidth_iconlen < f)
768 sbt_fixcolumnwidth_iconlen = f;
771 if(sbt_field_icon1 != "")
773 sz = draw_getimagesize(sbt_field_icon1);
775 if(sbt_fixcolumnwidth_iconlen < f)
776 sbt_fixcolumnwidth_iconlen = f;
779 if(sbt_field_icon2 != "")
781 sz = draw_getimagesize(sbt_field_icon2);
783 if(sbt_fixcolumnwidth_iconlen < f)
784 sbt_fixcolumnwidth_iconlen = f;
787 if(sbt_fixcolumnwidth_iconlen != 0)
789 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
790 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
793 sbt_fixcolumnwidth_marginlen = 0;
795 if(sbt_field[i] == SP_NAME) // name gets all remaining space
798 float remaining_space = 0;
799 for(j = 0; j < sbt_num_fields; ++j)
801 if (sbt_field[i] != SP_SEPARATOR)
802 remaining_space += sbt_field_size[j] + hud_fontsize.x;
803 sbt_field_size[i] = panel_size.x - remaining_space;
805 if (sbt_fixcolumnwidth_iconlen != 0)
806 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
807 float namesize = panel_size.x - remaining_space;
808 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
809 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
811 max_namesize = vid_conwidth - remaining_space;
814 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
816 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
817 if(sbt_field_size[i] < f)
818 sbt_field_size[i] = f;
823 void Scoreboard_initFieldSizes()
825 for(int i = 0; i < sbt_num_fields; ++i)
827 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
828 Scoreboard_FixColumnWidth(i, "");
832 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
835 vector column_dim = eY * panel_size.y;
837 column_dim.y -= 1.25 * hud_fontsize.y;
838 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
839 pos.x += hud_fontsize.x * 0.5;
840 for(i = 0; i < sbt_num_fields; ++i)
842 if(sbt_field[i] == SP_SEPARATOR)
844 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
847 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
848 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
849 pos.x += column_dim.x;
851 if(sbt_field[i] == SP_SEPARATOR)
853 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
854 for(i = sbt_num_fields - 1; i > 0; --i)
856 if(sbt_field[i] == SP_SEPARATOR)
859 pos.x -= sbt_field_size[i];
864 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);
868 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
869 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
870 pos.x -= hud_fontsize.x;
875 pos.y += 1.25 * hud_fontsize.y;
879 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
881 TC(bool, is_self); TC(int, pl_number);
883 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
885 vector h_pos = item_pos;
886 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
887 // alternated rows highlighting
889 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
890 else if((sbt_highlight) && (!(pl_number % 2)))
891 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
893 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
895 vector pos = item_pos;
896 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
898 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
900 pos.x += hud_fontsize.x * 0.5;
901 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
902 vector tmp = '0 0 0';
904 PlayerScoreField field;
905 for(i = 0; i < sbt_num_fields; ++i)
907 field = sbt_field[i];
908 if(field == SP_SEPARATOR)
911 if(is_spec && field != SP_NAME && field != SP_PING) {
912 pos.x += sbt_field_size[i] + hud_fontsize.x;
915 str = Scoreboard_GetField(pl, field);
916 str = Scoreboard_FixColumnWidth(i, str);
918 pos.x += sbt_field_size[i] + hud_fontsize.x;
920 if(field == SP_NAME) {
921 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
922 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
924 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
925 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 tmp.x = sbt_field_size[i] + hud_fontsize.x;
929 if(sbt_field_icon0 != "")
930 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
931 if(sbt_field_icon1 != "")
932 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
933 if(sbt_field_icon2 != "")
934 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
937 if(sbt_field[i] == SP_SEPARATOR)
939 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
940 for(i = sbt_num_fields-1; i > 0; --i)
942 field = sbt_field[i];
943 if(field == SP_SEPARATOR)
946 if(is_spec && field != SP_NAME && field != SP_PING) {
947 pos.x -= sbt_field_size[i] + hud_fontsize.x;
951 str = Scoreboard_GetField(pl, field);
952 str = Scoreboard_FixColumnWidth(i, str);
954 if(field == SP_NAME) {
955 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
956 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
958 tmp.x = sbt_fixcolumnwidth_len;
959 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
962 tmp.x = sbt_field_size[i];
963 if(sbt_field_icon0 != "")
964 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
965 if(sbt_field_icon1 != "")
966 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
967 if(sbt_field_icon2 != "")
968 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
969 pos.x -= sbt_field_size[i] + hud_fontsize.x;
974 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
977 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
980 vector h_pos = item_pos;
981 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
983 bool complete = (this_team == NUM_SPECTATOR);
986 if((sbt_highlight) && (!(pl_number % 2)))
987 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
989 vector pos = item_pos;
990 pos.x += hud_fontsize.x * 0.5;
991 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
993 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
995 width_limit -= stringwidth("...", false, hud_fontsize);
996 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
997 static float max_name_width = 0;
1000 float min_fieldsize = 0;
1001 float fieldpadding = hud_fontsize.x * 0.25;
1002 if(this_team == NUM_SPECTATOR)
1004 if(autocvar_hud_panel_scoreboard_spectators_showping)
1005 min_fieldsize = stringwidth("999", false, hud_fontsize);
1007 else if(autocvar_hud_panel_scoreboard_others_showscore)
1008 min_fieldsize = stringwidth("99", false, hud_fontsize);
1009 for(i = 0; pl; pl = pl.sort_next)
1011 if(pl.team != this_team)
1013 if(pl == ignored_pl)
1017 if(this_team == NUM_SPECTATOR)
1019 if(autocvar_hud_panel_scoreboard_spectators_showping)
1020 field = Scoreboard_GetField(pl, SP_PING);
1022 else if(autocvar_hud_panel_scoreboard_others_showscore)
1023 field = Scoreboard_GetField(pl, SP_SCORE);
1025 string str = entcs_GetName(pl.sv_entnum);
1026 if (autocvar_hud_panel_scoreboard_playerid)
1027 str = Scoreboard_AddPlayerId(str, pl);
1028 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1029 float column_width = stringwidth(str, true, hud_fontsize);
1030 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1032 if(column_width > max_name_width)
1033 max_name_width = column_width;
1034 column_width = max_name_width;
1038 fieldsize = stringwidth(field, false, hud_fontsize);
1039 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1042 if(pos.x + column_width > width_limit)
1047 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1052 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1053 pos.y += hud_fontsize.y * 1.25;
1057 vector name_pos = pos;
1058 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1059 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1060 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1064 h_size.y = hud_fontsize.y;
1065 vector field_pos = pos;
1066 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1067 field_pos.x += column_width - h_size.x;
1069 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1070 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1071 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1075 h_size.x = column_width + hud_fontsize.x * 0.25;
1076 h_size.y = hud_fontsize.y;
1077 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1079 pos.x += column_width;
1080 pos.x += hud_fontsize.x;
1082 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1085 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1087 int max_players = 999;
1088 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1090 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1093 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1094 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1095 height /= team_count;
1098 height -= panel_bg_padding * 2; // - padding
1099 max_players = floor(height / (hud_fontsize.y * 1.25));
1100 if(max_players <= 1)
1102 if(max_players == tm.team_size)
1107 entity me = playerslots[current_player];
1109 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1110 panel_size.y += panel_bg_padding * 2;
1113 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1114 if(panel.current_panel_bg != "0")
1115 end_pos.y += panel_bg_border * 2;
1117 if(panel_bg_padding)
1119 panel_pos += '1 1 0' * panel_bg_padding;
1120 panel_size -= '2 2 0' * panel_bg_padding;
1124 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1128 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1130 pos.y += 1.25 * hud_fontsize.y;
1133 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1135 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1138 // print header row and highlight columns
1139 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1141 // fill the table and draw the rows
1142 bool is_self = false;
1143 bool self_shown = false;
1145 for(pl = players.sort_next; pl; pl = pl.sort_next)
1147 if(pl.team != tm.team)
1149 if(i == max_players - 2 && pl != me)
1151 if(!self_shown && me.team == tm.team)
1153 Scoreboard_DrawItem(pos, rgb, me, true, i);
1155 pos.y += 1.25 * hud_fontsize.y;
1159 if(i >= max_players - 1)
1161 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1164 is_self = (pl.sv_entnum == current_player);
1165 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1168 pos.y += 1.25 * hud_fontsize.y;
1172 panel_size.x += panel_bg_padding * 2; // restore initial width
1176 bool Scoreboard_WouldDraw()
1178 if (MUTATOR_CALLHOOK(DrawScoreboard))
1180 else if (QuickMenu_IsOpened())
1182 else if (HUD_Radar_Clickable())
1184 else if (scoreboard_showscores)
1186 else if (intermission == 1)
1188 else if (intermission == 2)
1190 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1191 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1195 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1200 float average_accuracy;
1201 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1203 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1205 WepSet weapons_stat = WepSet_GetFromStat();
1206 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1207 int disownedcnt = 0;
1209 FOREACH(Weapons, it != WEP_Null, {
1210 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1212 WepSet set = it.m_wepset;
1213 if(it.spawnflags & WEP_TYPE_OTHER)
1218 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1220 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1227 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1228 if (weapon_cnt <= 0) return pos;
1231 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1233 int columnns = ceil(weapon_cnt / rows);
1235 float weapon_height = 29;
1236 float height = hud_fontsize.y + weapon_height;
1238 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);
1239 pos.y += 1.25 * hud_fontsize.y;
1240 if(panel.current_panel_bg != "0")
1241 pos.y += panel_bg_border;
1244 panel_size.y = height * rows;
1245 panel_size.y += panel_bg_padding * 2;
1247 float panel_bg_alpha_save = panel_bg_alpha;
1248 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1250 panel_bg_alpha = panel_bg_alpha_save;
1252 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1253 if(panel.current_panel_bg != "0")
1254 end_pos.y += panel_bg_border * 2;
1256 if(panel_bg_padding)
1258 panel_pos += '1 1 0' * panel_bg_padding;
1259 panel_size -= '2 2 0' * panel_bg_padding;
1263 vector tmp = panel_size;
1265 float weapon_width = tmp.x / columnns / rows;
1268 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1272 // column highlighting
1273 for (int i = 0; i < columnns; ++i)
1275 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);
1278 for (int i = 0; i < rows; ++i)
1279 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1282 average_accuracy = 0;
1283 int weapons_with_stats = 0;
1285 pos.x += weapon_width / 2;
1287 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1290 Accuracy_LoadColors();
1292 float oldposx = pos.x;
1296 FOREACH(Weapons, it != WEP_Null, {
1297 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1299 WepSet set = it.m_wepset;
1300 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1302 if (it.spawnflags & WEP_TYPE_OTHER)
1306 if (weapon_stats >= 0)
1307 weapon_alpha = sbt_fg_alpha;
1309 weapon_alpha = 0.2 * sbt_fg_alpha;
1312 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1314 if (weapon_stats >= 0) {
1315 weapons_with_stats += 1;
1316 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1319 s = sprintf("%d%%", weapon_stats * 100);
1322 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
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 == columnns - 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 uninteresting;
1348 STATIC_INIT(default_order_items_label)
1350 IL_EACH(default_order_items, true, {
1351 if(!(it.instanceOfPowerup
1352 || it == ITEM_HealthMega || it == ITEM_HealthBig
1353 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1356 it.uninteresting = true;
1361 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1363 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1365 int disowned_cnt = 0;
1366 int uninteresting_cnt = 0;
1367 IL_EACH(default_order_items, true, {
1368 int q = g_inventory.inv_items[it.m_id];
1369 //q = 1; // debug: display all items
1370 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1371 ++uninteresting_cnt;
1375 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1376 int n = items_cnt - disowned_cnt;
1377 if (n <= 0) return pos;
1379 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1380 int columnns = max(6, ceil(n / rows));
1383 float fontsize = height * 1/3;
1384 float item_height = height * 2/3;
1386 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1387 pos.y += 1.25 * hud_fontsize.y;
1388 if(panel.current_panel_bg != "0")
1389 pos.y += panel_bg_border;
1392 panel_size.y = height * rows;
1393 panel_size.y += panel_bg_padding * 2;
1395 float panel_bg_alpha_save = panel_bg_alpha;
1396 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1398 panel_bg_alpha = panel_bg_alpha_save;
1400 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1401 if(panel.current_panel_bg != "0")
1402 end_pos.y += panel_bg_border * 2;
1404 if(panel_bg_padding)
1406 panel_pos += '1 1 0' * panel_bg_padding;
1407 panel_size -= '2 2 0' * panel_bg_padding;
1411 vector tmp = panel_size;
1413 float item_width = tmp.x / columnns / rows;
1416 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1420 // column highlighting
1421 for (int i = 0; i < columnns; ++i)
1423 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1426 for (int i = 0; i < rows; ++i)
1427 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, rgb, sbt_highlight_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1431 pos.x += item_width / 2;
1433 float oldposx = pos.x;
1437 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1438 int n = g_inventory.inv_items[it.m_id];
1439 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1440 if (n <= 0) continue;
1441 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1443 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1444 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1445 tmpos.x += item_width * rows;
1446 pos.x += item_width * rows;
1447 if (rows == 2 && column == columnns - 1) {
1455 panel_size.x += panel_bg_padding * 2; // restore initial width
1460 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1462 pos.x += hud_fontsize.x * 0.25;
1463 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1464 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1465 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1467 pos.y += hud_fontsize.y;
1472 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1473 float stat_secrets_found, stat_secrets_total;
1474 float stat_monsters_killed, stat_monsters_total;
1478 // get monster stats
1479 stat_monsters_killed = STAT(MONSTERS_KILLED);
1480 stat_monsters_total = STAT(MONSTERS_TOTAL);
1482 // get secrets stats
1483 stat_secrets_found = STAT(SECRETS_FOUND);
1484 stat_secrets_total = STAT(SECRETS_TOTAL);
1486 // get number of rows
1487 if(stat_secrets_total)
1489 if(stat_monsters_total)
1492 // if no rows, return
1496 // draw table header
1497 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1498 pos.y += 1.25 * hud_fontsize.y;
1499 if(panel.current_panel_bg != "0")
1500 pos.y += panel_bg_border;
1503 panel_size.y = hud_fontsize.y * rows;
1504 panel_size.y += panel_bg_padding * 2;
1507 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1508 if(panel.current_panel_bg != "0")
1509 end_pos.y += panel_bg_border * 2;
1511 if(panel_bg_padding)
1513 panel_pos += '1 1 0' * panel_bg_padding;
1514 panel_size -= '2 2 0' * panel_bg_padding;
1518 vector tmp = panel_size;
1521 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1524 if(stat_monsters_total)
1526 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1527 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1531 if(stat_secrets_total)
1533 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1534 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1537 panel_size.x += panel_bg_padding * 2; // restore initial width
1542 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1545 RANKINGS_RECEIVED_CNT = 0;
1546 for (i=RANKINGS_CNT-1; i>=0; --i)
1548 ++RANKINGS_RECEIVED_CNT;
1550 if (RANKINGS_RECEIVED_CNT == 0)
1553 vector hl_rgb = rgb + '0.5 0.5 0.5';
1555 pos.y += hud_fontsize.y;
1556 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1557 pos.y += 1.25 * hud_fontsize.y;
1558 if(panel.current_panel_bg != "0")
1559 pos.y += panel_bg_border;
1564 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1566 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1571 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1573 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1577 float ranksize = 3 * hud_fontsize.x;
1578 float timesize = 5 * hud_fontsize.x;
1579 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1580 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1581 columns = min(columns, RANKINGS_RECEIVED_CNT);
1583 // expand name column to fill the entire row
1584 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1585 namesize += available_space;
1586 columnsize.x += available_space;
1588 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1589 panel_size.y += panel_bg_padding * 2;
1593 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1594 if(panel.current_panel_bg != "0")
1595 end_pos.y += panel_bg_border * 2;
1597 if(panel_bg_padding)
1599 panel_pos += '1 1 0' * panel_bg_padding;
1600 panel_size -= '2 2 0' * panel_bg_padding;
1606 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1608 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1610 int column = 0, j = 0;
1611 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1612 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1619 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1620 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1621 else if(!((j + column) & 1) && sbt_highlight)
1622 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1624 str = count_ordinal(i+1);
1625 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1626 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1627 str = ColorTranslateRGB(grecordholder[i]);
1629 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1630 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1632 pos.y += 1.25 * hud_fontsize.y;
1634 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1638 pos.x += panel_size.x / columns;
1639 pos.y = panel_pos.y;
1642 strfree(zoned_name_self);
1644 panel_size.x += panel_bg_padding * 2; // restore initial width
1648 float scoreboard_time;
1649 bool have_weapon_stats;
1650 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1652 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1654 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1657 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1658 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1664 if (!have_weapon_stats)
1666 FOREACH(Weapons, it != WEP_Null, {
1667 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1668 if (weapon_stats >= 0)
1670 have_weapon_stats = true;
1674 if (!have_weapon_stats)
1681 bool have_item_stats;
1682 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1684 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1686 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1689 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1690 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1696 if (!have_item_stats)
1698 IL_EACH(default_order_items, true, {
1699 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1701 int q = g_inventory.inv_items[it.m_id];
1702 //q = 1; // debug: display all items
1705 have_item_stats = true;
1710 if (!have_item_stats)
1717 void Scoreboard_Draw()
1719 if(!autocvar__hud_configure)
1721 if(!hud_draw_maximized) return;
1723 // frametime checks allow to toggle the scoreboard even when the game is paused
1724 if(scoreboard_active) {
1725 if (scoreboard_fade_alpha == 0)
1726 scoreboard_time = time;
1727 if(hud_configure_menu_open == 1)
1728 scoreboard_fade_alpha = 1;
1729 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1730 if (scoreboard_fadeinspeed && frametime)
1731 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1733 scoreboard_fade_alpha = 1;
1734 if(hud_fontsize_str != autocvar_hud_fontsize)
1736 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1737 Scoreboard_initFieldSizes();
1738 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1742 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1743 if (scoreboard_fadeoutspeed && frametime)
1744 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1746 scoreboard_fade_alpha = 0;
1749 if (!scoreboard_fade_alpha)
1751 scoreboard_acc_fade_alpha = 0;
1752 scoreboard_itemstats_fade_alpha = 0;
1757 scoreboard_fade_alpha = 0;
1759 if (autocvar_hud_panel_scoreboard_dynamichud)
1762 HUD_Scale_Disable();
1764 if(scoreboard_fade_alpha <= 0)
1766 panel_fade_alpha *= scoreboard_fade_alpha;
1767 HUD_Panel_LoadCvars();
1769 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1770 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1771 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1772 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1773 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1774 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1775 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1777 // don't overlap with con_notify
1778 if(!autocvar__hud_configure)
1779 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1781 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1782 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1783 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1784 panel_size.x = fixed_scoreboard_width;
1786 Scoreboard_UpdatePlayerTeams();
1788 float initial_pos_y = panel_pos.y;
1789 vector pos = panel_pos;
1794 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1796 // Begin of Game Info Section
1797 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1798 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1800 // Game Info: Game Type
1801 str = MapInfo_Type_ToText(gametype);
1802 draw_beginBoldFont();
1803 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);
1806 // Game Info: Game Detail
1807 float tl = STAT(TIMELIMIT);
1808 float fl = STAT(FRAGLIMIT);
1809 float ll = STAT(LEADLIMIT);
1810 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1813 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1814 if(!gametype.m_hidelimits)
1819 str = strcat(str, "^7 / "); // delimiter
1822 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1823 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1824 (teamscores_label(ts_primary) == "fastest") ? "" :
1825 TranslateScoresLabel(teamscores_label(ts_primary))));
1829 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1830 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1831 (scores_label(ps_primary) == "fastest") ? "" :
1832 TranslateScoresLabel(scores_label(ps_primary))));
1837 if(tl > 0 || fl > 0)
1840 if (ll_and_fl && fl > 0)
1841 str = strcat(str, "^7 & ");
1843 str = strcat(str, "^7 / ");
1848 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1849 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1850 (teamscores_label(ts_primary) == "fastest") ? "" :
1851 TranslateScoresLabel(teamscores_label(ts_primary))));
1855 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1856 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1857 (scores_label(ps_primary) == "fastest") ? "" :
1858 TranslateScoresLabel(scores_label(ps_primary))));
1863 pos.y += sb_gameinfo_type_fontsize.y;
1864 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
1866 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1867 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1868 // End of Game Info Section
1870 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1871 if(panel.current_panel_bg != "0")
1872 pos.y += panel_bg_border;
1874 // Draw the scoreboard
1875 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1878 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1882 vector panel_bg_color_save = panel_bg_color;
1883 vector team_score_baseoffset;
1884 vector team_size_baseoffset;
1885 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1887 // put team score to the left of scoreboard (and team size to the right)
1888 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1889 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1890 if(panel.current_panel_bg != "0")
1892 team_score_baseoffset.x -= panel_bg_border;
1893 team_size_baseoffset.x += panel_bg_border;
1898 // put team score to the right of scoreboard (and team size to the left)
1899 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1900 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1901 if(panel.current_panel_bg != "0")
1903 team_score_baseoffset.x += panel_bg_border;
1904 team_size_baseoffset.x -= panel_bg_border;
1908 int team_size_total = 0;
1909 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1911 // calculate team size total (sum of all team sizes)
1912 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1913 if(tm.team != NUM_SPECTATOR)
1914 team_size_total += tm.team_size;
1917 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1919 if(tm.team == NUM_SPECTATOR)
1924 draw_beginBoldFont();
1925 vector rgb = Team_ColorRGB(tm.team);
1926 str = ftos(tm.(teamscores(ts_primary)));
1927 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1929 // team score on the left (default)
1930 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1934 // team score on the right
1935 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1937 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1939 // team size (if set to show on the side)
1940 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1942 // calculate the starting position for the whole team size info string
1943 str = sprintf("%d/%d", tm.team_size, team_size_total);
1944 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1946 // team size on the left
1947 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1951 // team size on the right
1952 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1954 str = sprintf("%d", tm.team_size);
1955 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1956 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1957 str = sprintf("/%d", team_size_total);
1958 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1962 // secondary score, e.g. keyhunt
1963 if(ts_primary != ts_secondary)
1965 str = ftos(tm.(teamscores(ts_secondary)));
1966 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1969 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1974 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1977 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1980 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1981 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1982 else if(panel_bg_color_team > 0)
1983 panel_bg_color = rgb * panel_bg_color_team;
1985 panel_bg_color = rgb;
1986 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1988 panel_bg_color = panel_bg_color_save;
1992 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1993 if(tm.team != NUM_SPECTATOR)
1996 // display it anyway
1997 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2000 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2001 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2002 if (Scoreboard_ItemStats_WouldDraw(pos.y))
2003 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
2005 if(MUTATOR_CALLHOOK(ShowRankings)) {
2006 string ranktitle = M_ARGV(0, string);
2007 if(race_speedaward) {
2008 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2009 pos.y += 1.25 * hud_fontsize.y;
2011 if(race_speedaward_alltimebest) {
2012 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2013 pos.y += 1.25 * hud_fontsize.y;
2015 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2018 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2021 for(pl = players.sort_next; pl; pl = pl.sort_next)
2023 if(pl.team == NUM_SPECTATOR)
2025 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2026 if(tm.team == NUM_SPECTATOR)
2028 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2029 draw_beginBoldFont();
2030 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2032 pos.y += 1.25 * hud_fontsize.y;
2034 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2035 pos.y += 1.25 * hud_fontsize.y;
2042 // print information about respawn status
2043 float respawn_time = STAT(RESPAWN_TIME);
2047 if(respawn_time < 0)
2049 // a negative number means we are awaiting respawn, time value is still the same
2050 respawn_time *= -1; // remove mark now that we checked it
2052 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2053 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2055 str = sprintf(_("^1Respawning in ^3%s^1..."),
2056 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2057 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2059 count_seconds(ceil(respawn_time - time))
2063 else if(time < respawn_time)
2065 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2066 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2067 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2069 count_seconds(ceil(respawn_time - time))
2073 else if(time >= respawn_time)
2074 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2076 pos.y += 1.2 * hud_fontsize.y;
2077 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2080 pos.y += 2 * hud_fontsize.y;
2081 if (scoreboard_fade_alpha < 1)
2082 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2083 else if (pos.y != scoreboard_bottom)
2085 if (pos.y > scoreboard_bottom)
2086 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2088 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));