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 void Scoreboard_InitScores()
170 ps_primary = ps_secondary = NULL;
171 ts_primary = ts_secondary = -1;
172 FOREACH(Scores, true, {
173 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
174 if(f == SFL_SORT_PRIO_PRIMARY)
176 if(f == SFL_SORT_PRIO_SECONDARY)
179 if(ps_secondary == NULL)
180 ps_secondary = ps_primary;
182 for(i = 0; i < MAX_TEAMSCORE; ++i)
184 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
185 if(f == SFL_SORT_PRIO_PRIMARY)
187 if(f == SFL_SORT_PRIO_SECONDARY)
190 if(ts_secondary == -1)
191 ts_secondary = ts_primary;
193 Cmd_Scoreboard_SetFields(0);
197 void Scoreboard_UpdatePlayerTeams()
201 for(pl = players.sort_next; pl; pl = pl.sort_next)
204 int Team = entcs_GetScoreTeam(pl.sv_entnum);
205 if(SetTeam(pl, Team))
208 Scoreboard_UpdatePlayerPos(pl);
212 pl = players.sort_next;
217 print(strcat("PNUM: ", ftos(num), "\n"));
222 int Scoreboard_CompareScore(int vl, int vr, int f)
224 TC(int, vl); TC(int, vr); TC(int, f);
225 if(f & SFL_ZERO_IS_WORST)
227 if(vl == 0 && vr != 0)
229 if(vl != 0 && vr == 0)
233 return IS_INCREASING(f);
235 return IS_DECREASING(f);
239 float Scoreboard_ComparePlayerScores(entity left, entity right)
242 vl = entcs_GetTeam(left.sv_entnum);
243 vr = entcs_GetTeam(right.sv_entnum);
255 if(vl == NUM_SPECTATOR)
257 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
259 if(!left.gotscores && right.gotscores)
264 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
268 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
272 FOREACH(Scores, true, {
273 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
274 if (r >= 0) return r;
277 if (left.sv_entnum < right.sv_entnum)
283 void Scoreboard_UpdatePlayerPos(entity player)
286 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
288 SORT_SWAP(player, ent);
290 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
292 SORT_SWAP(ent, player);
296 float Scoreboard_CompareTeamScores(entity left, entity right)
300 if(left.team == NUM_SPECTATOR)
302 if(right.team == NUM_SPECTATOR)
305 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
309 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
313 for(i = 0; i < MAX_TEAMSCORE; ++i)
315 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
320 if (left.team < right.team)
326 void Scoreboard_UpdateTeamPos(entity Team)
329 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
331 SORT_SWAP(Team, ent);
333 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
335 SORT_SWAP(ent, Team);
339 void Cmd_Scoreboard_Help()
341 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
342 LOG_HELP(_("Usage:"));
343 LOG_HELP("^2scoreboard_columns_set ^3default");
344 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
345 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
346 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
347 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
348 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
349 LOG_HELP(_("The following field names are recognized (case insensitive):"));
355 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
356 "of game types, then a slash, to make the field show up only in these\n"
357 "or in all but these game types. You can also specify 'all' as a\n"
358 "field to show all fields available for the current game mode."));
361 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
362 "include/exclude ALL teams/noteams game modes."));
365 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
366 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
367 "right of the vertical bar aligned to the right."));
368 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
369 "other gamemodes except DM."));
372 // NOTE: adding a gametype with ? to not warn for an optional field
373 // make sure it's excluded in a previous exclusive rule, if any
374 // otherwise the previous exclusive rule warns anyway
375 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
376 #define SCOREBOARD_DEFAULT_COLUMNS \
377 "ping pl fps name |" \
378 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
379 " -teams,lms/deaths +ft,tdm/deaths" \
381 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
382 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
383 " +tdm,ft,dom,ons,as/teamkills"\
384 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
385 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
386 " +lms/lives +lms/rank" \
387 " +kh/kckills +kh/losses +kh/caps" \
388 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
389 " +as/objectives +nb/faults +nb/goals" \
390 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
391 " +dom/ticks +dom/takes" \
392 " -lms,rc,cts,inv,nb/score"
394 void Cmd_Scoreboard_SetFields(int argc)
399 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
403 return; // do nothing, we don't know gametype and scores yet
405 // sbt_fields uses strunzone on the titles!
406 if(!sbt_field_title[0])
407 for(i = 0; i < MAX_SBT_FIELDS; ++i)
408 sbt_field_title[i] = strzone("(null)");
410 // TODO: re enable with gametype dependant cvars?
411 if(argc < 3) // no arguments provided
412 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
415 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
419 if(argv(2) == "default" || argv(2) == "expand_default")
421 if(argv(2) == "expand_default")
422 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
423 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
425 else if(argv(2) == "all")
427 string s = "ping pl name |"; // scores without a label
428 FOREACH(Scores, true, {
430 if(it != ps_secondary)
431 if(scores_label(it) != "")
432 s = strcat(s, " ", scores_label(it));
434 if(ps_secondary != ps_primary)
435 s = strcat(s, " ", scores_label(ps_secondary));
436 s = strcat(s, " ", scores_label(ps_primary));
437 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
444 hud_fontsize = HUD_GetFontsize("hud_fontsize");
446 for(i = 1; i < argc - 1; ++i)
449 bool nocomplain = false;
450 if(substring(str, 0, 1) == "?")
453 str = substring(str, 1, strlen(str) - 1);
456 slash = strstrofs(str, "/", 0);
459 pattern = substring(str, 0, slash);
460 str = substring(str, slash + 1, strlen(str) - (slash + 1));
462 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
466 str = strtolower(str);
467 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
468 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
473 // fields without a label (not networked via the score system)
474 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
475 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
476 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
477 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
478 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
479 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
480 case "frags": sbt_field[sbt_num_fields] = SP_FRAGS; break;
481 default: // fields with a label
483 // map alternative labels
484 if (str == "damage") str = "dmg";
485 if (str == "damagetaken") str = "dmgtaken";
487 FOREACH(Scores, true, {
488 if (str == strtolower(scores_label(it))) {
490 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
494 // NOTE: can't check STAT(SHOWFPS) here, if checked too early it returns false anyway
495 if(!nocomplain && str != "fps") // server can disable the fps field
496 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
498 strfree(sbt_field_title[sbt_num_fields]);
499 sbt_field_size[sbt_num_fields] = 0;
503 sbt_field[sbt_num_fields] = j;
506 if(j == ps_secondary)
507 have_secondary = true;
512 if(sbt_num_fields >= MAX_SBT_FIELDS)
516 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
518 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
519 have_secondary = true;
520 if(ps_primary == ps_secondary)
521 have_secondary = true;
522 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
524 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
528 strfree(sbt_field_title[sbt_num_fields]);
529 for(i = sbt_num_fields; i > 0; --i)
531 sbt_field_title[i] = sbt_field_title[i-1];
532 sbt_field_size[i] = sbt_field_size[i-1];
533 sbt_field[i] = sbt_field[i-1];
535 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
536 sbt_field[0] = SP_NAME;
538 LOG_INFO("fixed missing field 'name'");
542 strfree(sbt_field_title[sbt_num_fields]);
543 for(i = sbt_num_fields; i > 1; --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[1] = strzone("|");
550 sbt_field[1] = SP_SEPARATOR;
551 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
553 LOG_INFO("fixed missing field '|'");
556 else if(!have_separator)
558 strcpy(sbt_field_title[sbt_num_fields], "|");
559 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
560 sbt_field[sbt_num_fields] = SP_SEPARATOR;
562 LOG_INFO("fixed missing field '|'");
566 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
567 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
568 sbt_field[sbt_num_fields] = ps_secondary;
570 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
574 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
575 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
576 sbt_field[sbt_num_fields] = ps_primary;
578 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
582 sbt_field[sbt_num_fields] = SP_END;
585 string Scoreboard_AddPlayerId(string pl_name, entity pl)
587 string pref = autocvar_hud_panel_scoreboard_playerid_prefix;
588 string suf = autocvar_hud_panel_scoreboard_playerid_suffix;
589 return strcat(pref, itos(pl.sv_entnum + 1), suf, pl_name);
593 vector sbt_field_rgb;
594 string sbt_field_icon0;
595 string sbt_field_icon1;
596 string sbt_field_icon2;
597 vector sbt_field_icon0_rgb;
598 vector sbt_field_icon1_rgb;
599 vector sbt_field_icon2_rgb;
600 string Scoreboard_GetName(entity pl)
602 if(ready_waiting && pl.ready)
604 sbt_field_icon0 = "gfx/scoreboard/player_ready";
608 int f = entcs_GetClientColors(pl.sv_entnum);
610 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
611 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
612 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
613 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
614 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617 return entcs_GetName(pl.sv_entnum);
620 string Scoreboard_GetField(entity pl, PlayerScoreField field)
622 float tmp, num, denom;
625 sbt_field_rgb = '1 1 1';
626 sbt_field_icon0 = "";
627 sbt_field_icon1 = "";
628 sbt_field_icon2 = "";
629 sbt_field_icon0_rgb = '1 1 1';
630 sbt_field_icon1_rgb = '1 1 1';
631 sbt_field_icon2_rgb = '1 1 1';
636 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
637 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
641 tmp = max(0, min(220, f-80)) / 220;
642 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
648 f = pl.ping_packetloss;
649 tmp = pl.ping_movementloss;
650 if(f == 0 && tmp == 0)
652 str = ftos(ceil(f * 100));
654 str = strcat(str, "~", ftos(ceil(tmp * 100)));
655 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
656 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
660 str = Scoreboard_GetName(pl);
661 if (autocvar_hud_panel_scoreboard_playerid)
662 str = Scoreboard_AddPlayerId(str, pl);
666 f = pl.(scores(SP_KILLS));
667 f -= pl.(scores(SP_SUICIDES));
671 num = pl.(scores(SP_KILLS));
672 denom = pl.(scores(SP_DEATHS));
675 sbt_field_rgb = '0 1 0';
676 str = sprintf("%d", num);
677 } else if(num <= 0) {
678 sbt_field_rgb = '1 0 0';
679 str = sprintf("%.1f", num/denom);
681 str = sprintf("%.1f", num/denom);
685 f = pl.(scores(SP_KILLS));
686 f -= pl.(scores(SP_DEATHS));
689 sbt_field_rgb = '0 1 0';
691 sbt_field_rgb = '1 1 1';
693 sbt_field_rgb = '1 0 0';
699 float elo = pl.(scores(SP_ELO));
701 case -1: return "...";
702 case -2: return _("N/A");
703 default: return ftos(elo);
709 float fps = pl.(scores(SP_FPS));
712 sbt_field_rgb = '1 1 1';
713 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
715 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
716 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
720 case SP_DMG: case SP_DMGTAKEN:
721 return sprintf("%.1f k", pl.(scores(field)) / 1000);
723 default: case SP_SCORE:
724 tmp = pl.(scores(field));
725 f = scores_flags(field);
726 if(field == ps_primary)
727 sbt_field_rgb = '1 1 0';
728 else if(field == ps_secondary)
729 sbt_field_rgb = '0 1 1';
731 sbt_field_rgb = '1 1 1';
732 return ScoreString(f, tmp);
737 float sbt_fixcolumnwidth_len;
738 float sbt_fixcolumnwidth_iconlen;
739 float sbt_fixcolumnwidth_marginlen;
741 string Scoreboard_FixColumnWidth(int i, string str)
747 sbt_fixcolumnwidth_iconlen = 0;
749 if(sbt_field_icon0 != "")
751 sz = draw_getimagesize(sbt_field_icon0);
753 if(sbt_fixcolumnwidth_iconlen < f)
754 sbt_fixcolumnwidth_iconlen = f;
757 if(sbt_field_icon1 != "")
759 sz = draw_getimagesize(sbt_field_icon1);
761 if(sbt_fixcolumnwidth_iconlen < f)
762 sbt_fixcolumnwidth_iconlen = f;
765 if(sbt_field_icon2 != "")
767 sz = draw_getimagesize(sbt_field_icon2);
769 if(sbt_fixcolumnwidth_iconlen < f)
770 sbt_fixcolumnwidth_iconlen = f;
773 if(sbt_fixcolumnwidth_iconlen != 0)
775 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
776 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
779 sbt_fixcolumnwidth_marginlen = 0;
781 if(sbt_field[i] == SP_NAME) // name gets all remaining space
784 float remaining_space = 0;
785 for(j = 0; j < sbt_num_fields; ++j)
787 if (sbt_field[i] != SP_SEPARATOR)
788 remaining_space += sbt_field_size[j] + hud_fontsize.x;
789 sbt_field_size[i] = panel_size.x - remaining_space;
791 if (sbt_fixcolumnwidth_iconlen != 0)
792 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
793 float namesize = panel_size.x - remaining_space;
794 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
795 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
797 max_namesize = vid_conwidth - remaining_space;
800 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
802 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
803 if(sbt_field_size[i] < f)
804 sbt_field_size[i] = f;
809 void Scoreboard_initFieldSizes()
811 for(int i = 0; i < sbt_num_fields; ++i)
813 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
814 Scoreboard_FixColumnWidth(i, "");
818 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
821 vector column_dim = eY * panel_size.y;
823 column_dim.y -= 1.25 * hud_fontsize.y;
824 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
825 pos.x += hud_fontsize.x * 0.5;
826 for(i = 0; i < sbt_num_fields; ++i)
828 if(sbt_field[i] == SP_SEPARATOR)
830 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
833 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
834 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
835 pos.x += column_dim.x;
837 if(sbt_field[i] == SP_SEPARATOR)
839 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
840 for(i = sbt_num_fields - 1; i > 0; --i)
842 if(sbt_field[i] == SP_SEPARATOR)
845 pos.x -= sbt_field_size[i];
850 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
851 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
854 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
855 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
856 pos.x -= hud_fontsize.x;
861 pos.y += 1.25 * hud_fontsize.y;
865 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
867 TC(bool, is_self); TC(int, pl_number);
869 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
871 vector h_pos = item_pos;
872 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
873 // alternated rows highlighting
875 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
876 else if((sbt_highlight) && (!(pl_number % 2)))
877 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
879 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
881 vector pos = item_pos;
882 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
884 drawstring(pos + eX * (panel_size.x + 0.5 * hud_fontsize.x) + eY, "\xE2\x97\x80", hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
886 pos.x += hud_fontsize.x * 0.5;
887 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
888 vector tmp = '0 0 0';
890 PlayerScoreField field;
891 for(i = 0; i < sbt_num_fields; ++i)
893 field = sbt_field[i];
894 if(field == SP_SEPARATOR)
897 if(is_spec && field != SP_NAME && field != SP_PING) {
898 pos.x += sbt_field_size[i] + hud_fontsize.x;
901 str = Scoreboard_GetField(pl, field);
902 str = Scoreboard_FixColumnWidth(i, str);
904 pos.x += sbt_field_size[i] + hud_fontsize.x;
906 if(field == SP_NAME) {
907 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
908 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
910 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
911 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
914 tmp.x = sbt_field_size[i] + hud_fontsize.x;
915 if(sbt_field_icon0 != "")
916 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
917 if(sbt_field_icon1 != "")
918 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
919 if(sbt_field_icon2 != "")
920 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
923 if(sbt_field[i] == SP_SEPARATOR)
925 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
926 for(i = sbt_num_fields-1; i > 0; --i)
928 field = sbt_field[i];
929 if(field == SP_SEPARATOR)
932 if(is_spec && field != SP_NAME && field != SP_PING) {
933 pos.x -= sbt_field_size[i] + hud_fontsize.x;
937 str = Scoreboard_GetField(pl, field);
938 str = Scoreboard_FixColumnWidth(i, str);
940 if(field == SP_NAME) {
941 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
942 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
944 tmp.x = sbt_fixcolumnwidth_len;
945 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
948 tmp.x = sbt_field_size[i];
949 if(sbt_field_icon0 != "")
950 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
951 if(sbt_field_icon1 != "")
952 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
953 if(sbt_field_icon2 != "")
954 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
955 pos.x -= sbt_field_size[i] + hud_fontsize.x;
960 drawfill(h_pos, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
963 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
966 vector h_pos = item_pos;
967 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
969 bool complete = (this_team == NUM_SPECTATOR);
972 if((sbt_highlight) && (!(pl_number % 2)))
973 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
975 vector pos = item_pos;
976 pos.x += hud_fontsize.x * 0.5;
977 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
979 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
981 width_limit -= stringwidth("...", false, hud_fontsize);
982 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
983 static float max_name_width = 0;
986 float min_fieldsize = 0;
987 float fieldpadding = hud_fontsize.x * 0.25;
988 if(this_team == NUM_SPECTATOR)
990 if(autocvar_hud_panel_scoreboard_spectators_showping)
991 min_fieldsize = stringwidth("999", false, hud_fontsize);
993 else if(autocvar_hud_panel_scoreboard_others_showscore)
994 min_fieldsize = stringwidth("99", false, hud_fontsize);
995 for(i = 0; pl; pl = pl.sort_next)
997 if(pl.team != this_team)
1003 if(this_team == NUM_SPECTATOR)
1005 if(autocvar_hud_panel_scoreboard_spectators_showping)
1006 field = Scoreboard_GetField(pl, SP_PING);
1008 else if(autocvar_hud_panel_scoreboard_others_showscore)
1009 field = Scoreboard_GetField(pl, SP_SCORE);
1011 string str = entcs_GetName(pl.sv_entnum);
1012 if (autocvar_hud_panel_scoreboard_playerid)
1013 str = Scoreboard_AddPlayerId(str, pl);
1014 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1015 float column_width = stringwidth(str, true, hud_fontsize);
1016 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1018 if(column_width > max_name_width)
1019 max_name_width = column_width;
1020 column_width = max_name_width;
1024 fieldsize = stringwidth(field, false, hud_fontsize);
1025 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1028 if(pos.x + column_width > width_limit)
1033 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1038 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1039 pos.y += hud_fontsize.y * 1.25;
1043 vector name_pos = pos;
1044 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1045 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1046 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1049 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1050 h_size.y = hud_fontsize.y;
1051 vector field_pos = pos;
1052 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1053 field_pos.x += column_width - h_size.x;
1055 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1056 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1057 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1061 h_size.x = column_width + hud_fontsize.x * 0.25;
1062 h_size.y = hud_fontsize.y;
1063 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', sbt_highlight_alpha_eliminated, DRAWFLAG_NORMAL);
1065 pos.x += column_width;
1066 pos.x += hud_fontsize.x;
1068 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1071 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1073 int max_players = 999;
1074 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1076 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1079 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1080 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1081 height /= team_count;
1084 height -= panel_bg_padding * 2; // - padding
1085 max_players = floor(height / (hud_fontsize.y * 1.25));
1086 if(max_players <= 1)
1088 if(max_players == tm.team_size)
1093 entity me = playerslots[current_player];
1095 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1096 panel_size.y += panel_bg_padding * 2;
1099 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1100 if(panel.current_panel_bg != "0")
1101 end_pos.y += panel_bg_border * 2;
1103 if(panel_bg_padding)
1105 panel_pos += '1 1 0' * panel_bg_padding;
1106 panel_size -= '2 2 0' * panel_bg_padding;
1110 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1114 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1116 pos.y += 1.25 * hud_fontsize.y;
1119 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1121 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1124 // print header row and highlight columns
1125 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1127 // fill the table and draw the rows
1128 bool is_self = false;
1129 bool self_shown = false;
1131 for(pl = players.sort_next; pl; pl = pl.sort_next)
1133 if(pl.team != tm.team)
1135 if(i == max_players - 2 && pl != me)
1137 if(!self_shown && me.team == tm.team)
1139 Scoreboard_DrawItem(pos, rgb, me, true, i);
1141 pos.y += 1.25 * hud_fontsize.y;
1145 if(i >= max_players - 1)
1147 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1150 is_self = (pl.sv_entnum == current_player);
1151 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1154 pos.y += 1.25 * hud_fontsize.y;
1158 panel_size.x += panel_bg_padding * 2; // restore initial width
1162 bool Scoreboard_WouldDraw()
1164 if (MUTATOR_CALLHOOK(DrawScoreboard))
1166 else if (QuickMenu_IsOpened())
1168 else if (HUD_Radar_Clickable())
1170 else if (scoreboard_showscores)
1172 else if (intermission == 1)
1174 else if (intermission == 2)
1176 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1177 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1181 else if (scoreboard_showscores_force || MUTATOR_CALLHOOK(DrawScoreboard_Force))
1186 float average_accuracy;
1187 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1189 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1191 WepSet weapons_stat = WepSet_GetFromStat();
1192 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1193 int disownedcnt = 0;
1195 FOREACH(Weapons, it != WEP_Null, {
1196 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1198 WepSet set = it.m_wepset;
1199 if(it.spawnflags & WEP_TYPE_OTHER)
1204 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1206 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1213 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1214 if (weapon_cnt <= 0) return pos;
1217 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1219 int columnns = ceil(weapon_cnt / rows);
1221 float weapon_height = 29;
1222 float height = hud_fontsize.y + weapon_height;
1224 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);
1225 pos.y += 1.25 * hud_fontsize.y;
1226 if(panel.current_panel_bg != "0")
1227 pos.y += panel_bg_border;
1230 panel_size.y = height * rows;
1231 panel_size.y += panel_bg_padding * 2;
1233 float panel_bg_alpha_save = panel_bg_alpha;
1234 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1236 panel_bg_alpha = panel_bg_alpha_save;
1238 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1239 if(panel.current_panel_bg != "0")
1240 end_pos.y += panel_bg_border * 2;
1242 if(panel_bg_padding)
1244 panel_pos += '1 1 0' * panel_bg_padding;
1245 panel_size -= '2 2 0' * panel_bg_padding;
1249 vector tmp = panel_size;
1251 float weapon_width = tmp.x / columnns / rows;
1254 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1258 // column highlighting
1259 for (int i = 0; i < columnns; ++i)
1261 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);
1264 for (int i = 0; i < rows; ++i)
1265 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1268 average_accuracy = 0;
1269 int weapons_with_stats = 0;
1271 pos.x += weapon_width / 2;
1273 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1276 Accuracy_LoadColors();
1278 float oldposx = pos.x;
1282 FOREACH(Weapons, it != WEP_Null, {
1283 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1285 WepSet set = it.m_wepset;
1286 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1288 if (it.spawnflags & WEP_TYPE_OTHER)
1292 if (weapon_stats >= 0)
1293 weapon_alpha = sbt_fg_alpha;
1295 weapon_alpha = 0.2 * sbt_fg_alpha;
1298 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1300 if (weapon_stats >= 0) {
1301 weapons_with_stats += 1;
1302 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1305 s = sprintf("%d%%", weapon_stats * 100);
1308 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1310 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1311 rgb = Accuracy_GetColor(weapon_stats);
1313 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1315 tmpos.x += weapon_width * rows;
1316 pos.x += weapon_width * rows;
1317 if (rows == 2 && column == columnns - 1) {
1325 if (weapons_with_stats)
1326 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1328 panel_size.x += panel_bg_padding * 2; // restore initial width
1333 .bool uninteresting;
1334 STATIC_INIT(default_order_items_label)
1336 IL_EACH(default_order_items, true, {
1337 if(!(it.instanceOfPowerup
1338 || it == ITEM_HealthMega || it == ITEM_HealthBig
1339 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1342 it.uninteresting = true;
1347 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1349 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1351 int disowned_cnt = 0;
1352 int uninteresting_cnt = 0;
1353 IL_EACH(default_order_items, true, {
1354 int q = g_inventory.inv_items[it.m_id];
1355 //q = 1; // debug: display all items
1356 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1357 ++uninteresting_cnt;
1361 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1362 int n = items_cnt - disowned_cnt;
1363 if (n <= 0) return pos;
1365 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1366 int columnns = max(6, ceil(n / rows));
1369 float fontsize = height * 1/3;
1370 float item_height = height * 2/3;
1372 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1373 pos.y += 1.25 * hud_fontsize.y;
1374 if(panel.current_panel_bg != "0")
1375 pos.y += panel_bg_border;
1378 panel_size.y = height * rows;
1379 panel_size.y += panel_bg_padding * 2;
1381 float panel_bg_alpha_save = panel_bg_alpha;
1382 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1384 panel_bg_alpha = panel_bg_alpha_save;
1386 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1387 if(panel.current_panel_bg != "0")
1388 end_pos.y += panel_bg_border * 2;
1390 if(panel_bg_padding)
1392 panel_pos += '1 1 0' * panel_bg_padding;
1393 panel_size -= '2 2 0' * panel_bg_padding;
1397 vector tmp = panel_size;
1399 float item_width = tmp.x / columnns / rows;
1402 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1406 // column highlighting
1407 for (int i = 0; i < columnns; ++i)
1409 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);
1412 for (int i = 0; i < rows; ++i)
1413 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);
1417 pos.x += item_width / 2;
1419 float oldposx = pos.x;
1423 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1424 int n = g_inventory.inv_items[it.m_id];
1425 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1426 if (n <= 0) continue;
1427 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);
1429 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1430 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);
1431 tmpos.x += item_width * rows;
1432 pos.x += item_width * rows;
1433 if (rows == 2 && column == columnns - 1) {
1441 panel_size.x += panel_bg_padding * 2; // restore initial width
1446 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1448 pos.x += hud_fontsize.x * 0.25;
1449 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1450 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1451 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1453 pos.y += hud_fontsize.y;
1458 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1459 float stat_secrets_found, stat_secrets_total;
1460 float stat_monsters_killed, stat_monsters_total;
1464 // get monster stats
1465 stat_monsters_killed = STAT(MONSTERS_KILLED);
1466 stat_monsters_total = STAT(MONSTERS_TOTAL);
1468 // get secrets stats
1469 stat_secrets_found = STAT(SECRETS_FOUND);
1470 stat_secrets_total = STAT(SECRETS_TOTAL);
1472 // get number of rows
1473 if(stat_secrets_total)
1475 if(stat_monsters_total)
1478 // if no rows, return
1482 // draw table header
1483 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1484 pos.y += 1.25 * hud_fontsize.y;
1485 if(panel.current_panel_bg != "0")
1486 pos.y += panel_bg_border;
1489 panel_size.y = hud_fontsize.y * rows;
1490 panel_size.y += panel_bg_padding * 2;
1493 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1494 if(panel.current_panel_bg != "0")
1495 end_pos.y += panel_bg_border * 2;
1497 if(panel_bg_padding)
1499 panel_pos += '1 1 0' * panel_bg_padding;
1500 panel_size -= '2 2 0' * panel_bg_padding;
1504 vector tmp = panel_size;
1507 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1510 if(stat_monsters_total)
1512 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1513 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1517 if(stat_secrets_total)
1519 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1520 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1523 panel_size.x += panel_bg_padding * 2; // restore initial width
1528 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1531 RANKINGS_RECEIVED_CNT = 0;
1532 for (i=RANKINGS_CNT-1; i>=0; --i)
1534 ++RANKINGS_RECEIVED_CNT;
1536 if (RANKINGS_RECEIVED_CNT == 0)
1539 vector hl_rgb = rgb + '0.5 0.5 0.5';
1541 pos.y += hud_fontsize.y;
1542 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1543 pos.y += 1.25 * hud_fontsize.y;
1544 if(panel.current_panel_bg != "0")
1545 pos.y += panel_bg_border;
1550 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1552 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1557 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1559 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1563 float ranksize = 3 * hud_fontsize.x;
1564 float timesize = 5 * hud_fontsize.x;
1565 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1566 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1567 columns = min(columns, RANKINGS_RECEIVED_CNT);
1569 // expand name column to fill the entire row
1570 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1571 namesize += available_space;
1572 columnsize.x += available_space;
1574 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1575 panel_size.y += panel_bg_padding * 2;
1579 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1580 if(panel.current_panel_bg != "0")
1581 end_pos.y += panel_bg_border * 2;
1583 if(panel_bg_padding)
1585 panel_pos += '1 1 0' * panel_bg_padding;
1586 panel_size -= '2 2 0' * panel_bg_padding;
1592 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1594 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1596 int column = 0, j = 0;
1597 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1598 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1605 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1606 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1607 else if(!((j + column) & 1) && sbt_highlight)
1608 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1610 str = count_ordinal(i+1);
1611 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1612 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1613 str = ColorTranslateRGB(grecordholder[i]);
1615 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1616 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1618 pos.y += 1.25 * hud_fontsize.y;
1620 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1624 pos.x += panel_size.x / columns;
1625 pos.y = panel_pos.y;
1628 strfree(zoned_name_self);
1630 panel_size.x += panel_bg_padding * 2; // restore initial width
1634 float scoreboard_time;
1635 bool have_weapon_stats;
1636 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1638 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1640 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1643 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1644 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1650 if (!have_weapon_stats)
1652 FOREACH(Weapons, it != WEP_Null, {
1653 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1654 if (weapon_stats >= 0)
1656 have_weapon_stats = true;
1660 if (!have_weapon_stats)
1667 bool have_item_stats;
1668 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1670 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1672 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1675 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1676 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1682 if (!have_item_stats)
1684 IL_EACH(default_order_items, true, {
1685 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1687 int q = g_inventory.inv_items[it.m_id];
1688 //q = 1; // debug: display all items
1691 have_item_stats = true;
1696 if (!have_item_stats)
1703 void Scoreboard_Draw()
1705 if(!autocvar__hud_configure)
1707 if(!hud_draw_maximized) return;
1709 // frametime checks allow to toggle the scoreboard even when the game is paused
1710 if(scoreboard_active) {
1711 if (scoreboard_fade_alpha == 0)
1712 scoreboard_time = time;
1713 if(hud_configure_menu_open == 1)
1714 scoreboard_fade_alpha = 1;
1715 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1716 if (scoreboard_fadeinspeed && frametime)
1717 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1719 scoreboard_fade_alpha = 1;
1720 if(hud_fontsize_str != autocvar_hud_fontsize)
1722 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1723 Scoreboard_initFieldSizes();
1724 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1728 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1729 if (scoreboard_fadeoutspeed && frametime)
1730 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1732 scoreboard_fade_alpha = 0;
1735 if (!scoreboard_fade_alpha)
1737 scoreboard_acc_fade_alpha = 0;
1738 scoreboard_itemstats_fade_alpha = 0;
1743 scoreboard_fade_alpha = 0;
1745 if (autocvar_hud_panel_scoreboard_dynamichud)
1748 HUD_Scale_Disable();
1750 if(scoreboard_fade_alpha <= 0)
1752 panel_fade_alpha *= scoreboard_fade_alpha;
1753 HUD_Panel_LoadCvars();
1755 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1756 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1757 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1758 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1759 sbt_highlight_alpha_eliminated = autocvar_hud_panel_scoreboard_table_highlight_alpha_eliminated * panel_fg_alpha;
1760 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1761 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1763 // don't overlap with con_notify
1764 if(!autocvar__hud_configure)
1765 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1767 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1768 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1769 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1770 panel_size.x = fixed_scoreboard_width;
1772 Scoreboard_UpdatePlayerTeams();
1774 float initial_pos_y = panel_pos.y;
1775 vector pos = panel_pos;
1780 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1782 // Begin of Game Info Section
1783 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1784 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1786 // Game Info: Game Type
1787 str = MapInfo_Type_ToText(gametype);
1788 draw_beginBoldFont();
1789 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);
1792 // Game Info: Game Detail
1793 float tl = STAT(TIMELIMIT);
1794 float fl = STAT(FRAGLIMIT);
1795 float ll = STAT(LEADLIMIT);
1796 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1799 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1800 if(!gametype.m_hidelimits)
1805 str = strcat(str, "^7 / "); // delimiter
1808 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1809 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1810 (teamscores_label(ts_primary) == "fastest") ? "" :
1811 TranslateScoresLabel(teamscores_label(ts_primary))));
1815 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1816 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1817 (scores_label(ps_primary) == "fastest") ? "" :
1818 TranslateScoresLabel(scores_label(ps_primary))));
1823 if(tl > 0 || fl > 0)
1826 if (ll_and_fl && fl > 0)
1827 str = strcat(str, "^7 & ");
1829 str = strcat(str, "^7 / ");
1834 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1835 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1836 (teamscores_label(ts_primary) == "fastest") ? "" :
1837 TranslateScoresLabel(teamscores_label(ts_primary))));
1841 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1842 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1843 (scores_label(ps_primary) == "fastest") ? "" :
1844 TranslateScoresLabel(scores_label(ps_primary))));
1849 pos.y += sb_gameinfo_type_fontsize.y;
1850 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
1852 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1853 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1854 // End of Game Info Section
1856 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1857 if(panel.current_panel_bg != "0")
1858 pos.y += panel_bg_border;
1860 // Draw the scoreboard
1861 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1864 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1868 vector panel_bg_color_save = panel_bg_color;
1869 vector team_score_baseoffset;
1870 vector team_size_baseoffset;
1871 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1873 // put team score to the left of scoreboard (and team size to the right)
1874 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1875 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1876 if(panel.current_panel_bg != "0")
1878 team_score_baseoffset.x -= panel_bg_border;
1879 team_size_baseoffset.x += panel_bg_border;
1884 // put team score to the right of scoreboard (and team size to the left)
1885 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1886 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1887 if(panel.current_panel_bg != "0")
1889 team_score_baseoffset.x += panel_bg_border;
1890 team_size_baseoffset.x -= panel_bg_border;
1894 int team_size_total = 0;
1895 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1897 // calculate team size total (sum of all team sizes)
1898 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1899 if(tm.team != NUM_SPECTATOR)
1900 team_size_total += tm.team_size;
1903 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1905 if(tm.team == NUM_SPECTATOR)
1910 draw_beginBoldFont();
1911 vector rgb = Team_ColorRGB(tm.team);
1912 str = ftos(tm.(teamscores(ts_primary)));
1913 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1915 // team score on the left (default)
1916 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1920 // team score on the right
1921 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1923 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1925 // team size (if set to show on the side)
1926 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1928 // calculate the starting position for the whole team size info string
1929 str = sprintf("%d/%d", tm.team_size, team_size_total);
1930 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1932 // team size on the left
1933 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1937 // team size on the right
1938 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1940 str = sprintf("%d", tm.team_size);
1941 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1942 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1943 str = sprintf("/%d", team_size_total);
1944 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1948 // secondary score, e.g. keyhunt
1949 if(ts_primary != ts_secondary)
1951 str = ftos(tm.(teamscores(ts_secondary)));
1952 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1955 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1960 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1963 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1966 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1967 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1968 else if(panel_bg_color_team > 0)
1969 panel_bg_color = rgb * panel_bg_color_team;
1971 panel_bg_color = rgb;
1972 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1974 panel_bg_color = panel_bg_color_save;
1978 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1979 if(tm.team != NUM_SPECTATOR)
1982 // display it anyway
1983 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1986 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1987 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1988 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1989 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1991 if(MUTATOR_CALLHOOK(ShowRankings)) {
1992 string ranktitle = M_ARGV(0, string);
1993 if(race_speedaward) {
1994 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);
1995 pos.y += 1.25 * hud_fontsize.y;
1997 if(race_speedaward_alltimebest) {
1998 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);
1999 pos.y += 1.25 * hud_fontsize.y;
2001 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2004 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2007 for(pl = players.sort_next; pl; pl = pl.sort_next)
2009 if(pl.team == NUM_SPECTATOR)
2011 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2012 if(tm.team == NUM_SPECTATOR)
2014 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2015 draw_beginBoldFont();
2016 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2018 pos.y += 1.25 * hud_fontsize.y;
2020 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2021 pos.y += 1.25 * hud_fontsize.y;
2028 // print information about respawn status
2029 float respawn_time = STAT(RESPAWN_TIME);
2033 if(respawn_time < 0)
2035 // a negative number means we are awaiting respawn, time value is still the same
2036 respawn_time *= -1; // remove mark now that we checked it
2038 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2039 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2041 str = sprintf(_("^1Respawning in ^3%s^1..."),
2042 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2043 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2045 count_seconds(ceil(respawn_time - time))
2049 else if(time < respawn_time)
2051 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2052 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2053 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2055 count_seconds(ceil(respawn_time - time))
2059 else if(time >= respawn_time)
2060 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2062 pos.y += 1.2 * hud_fontsize.y;
2063 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2066 pos.y += 2 * hud_fontsize.y;
2067 if (scoreboard_fade_alpha < 1)
2068 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2069 else if (pos.y != scoreboard_bottom)
2071 if (pos.y > scoreboard_bottom)
2072 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2074 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));