1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/draw.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_bg_teams_color_team");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
35 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
38 const int MAX_SBT_FIELDS = MAX_SCORE;
40 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
41 float sbt_field_size[MAX_SBT_FIELDS + 1];
42 string sbt_field_title[MAX_SBT_FIELDS + 1];
45 string autocvar_hud_fontsize;
46 string hud_fontsize_str;
51 float sbt_fg_alpha_self;
53 float sbt_highlight_alpha;
54 float sbt_highlight_alpha_self;
56 // provide basic panel cvars to old clients
57 // TODO remove them after a future release (0.8.2+)
58 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
59 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
60 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
61 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
62 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
63 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
64 noref string autocvar_hud_panel_scoreboard_bg_border = "";
65 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
67 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
68 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
69 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
70 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
71 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
72 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
73 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
74 bool autocvar_hud_panel_scoreboard_table_highlight = true;
75 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
76 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
77 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
78 float autocvar_hud_panel_scoreboard_namesize = 15;
79 float autocvar_hud_panel_scoreboard_team_size_position = 0;
81 bool autocvar_hud_panel_scoreboard_accuracy = true;
82 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
83 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
84 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
85 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
87 bool autocvar_hud_panel_scoreboard_itemstats = true;
88 bool autocvar_hud_panel_scoreboard_itemstats_doublerows = false;
89 bool autocvar_hud_panel_scoreboard_itemstats_filter = true;
90 float autocvar_hud_panel_scoreboard_itemstats_showdelay = 2.2; // slightly more delayed than accuracy
91 float autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos = 0.75;
93 bool autocvar_hud_panel_scoreboard_dynamichud = false;
95 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
96 bool autocvar_hud_panel_scoreboard_others_showscore = true;
97 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
98 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
99 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
101 // mode 0: returns translated label
102 // mode 1: prints name and description of all the labels
103 string Label_getInfo(string label, int mode)
106 label = "bckills"; // first case in the switch
110 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
111 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
112 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")));
113 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
114 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
115 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
116 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
117 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
118 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
119 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
120 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
121 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
122 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
123 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
124 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
125 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
126 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
127 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
128 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
129 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
130 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
131 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
132 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
133 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
134 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
135 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
136 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
137 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")));
138 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
139 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
140 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
141 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
142 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
143 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
144 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
145 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
146 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
147 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
148 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
149 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
150 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
151 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
152 default: return label;
157 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
158 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
160 void Scoreboard_InitScores()
164 ps_primary = ps_secondary = NULL;
165 ts_primary = ts_secondary = -1;
166 FOREACH(Scores, true, {
167 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
168 if(f == SFL_SORT_PRIO_PRIMARY)
170 if(f == SFL_SORT_PRIO_SECONDARY)
173 if(ps_secondary == NULL)
174 ps_secondary = ps_primary;
176 for(i = 0; i < MAX_TEAMSCORE; ++i)
178 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
179 if(f == SFL_SORT_PRIO_PRIMARY)
181 if(f == SFL_SORT_PRIO_SECONDARY)
184 if(ts_secondary == -1)
185 ts_secondary = ts_primary;
187 Cmd_Scoreboard_SetFields(0);
191 void Scoreboard_UpdatePlayerTeams()
195 for(pl = players.sort_next; pl; pl = pl.sort_next)
198 int Team = entcs_GetScoreTeam(pl.sv_entnum);
199 if(SetTeam(pl, Team))
202 Scoreboard_UpdatePlayerPos(pl);
206 pl = players.sort_next;
211 print(strcat("PNUM: ", ftos(num), "\n"));
216 int Scoreboard_CompareScore(int vl, int vr, int f)
218 TC(int, vl); TC(int, vr); TC(int, f);
219 if(f & SFL_ZERO_IS_WORST)
221 if(vl == 0 && vr != 0)
223 if(vl != 0 && vr == 0)
227 return IS_INCREASING(f);
229 return IS_DECREASING(f);
233 float Scoreboard_ComparePlayerScores(entity left, entity right)
236 vl = entcs_GetTeam(left.sv_entnum);
237 vr = entcs_GetTeam(right.sv_entnum);
249 if(vl == NUM_SPECTATOR)
251 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
253 if(!left.gotscores && right.gotscores)
258 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
262 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
266 FOREACH(Scores, true, {
267 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
268 if (r >= 0) return r;
271 if (left.sv_entnum < right.sv_entnum)
277 void Scoreboard_UpdatePlayerPos(entity player)
280 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
282 SORT_SWAP(player, ent);
284 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
286 SORT_SWAP(ent, player);
290 float Scoreboard_CompareTeamScores(entity left, entity right)
294 if(left.team == NUM_SPECTATOR)
296 if(right.team == NUM_SPECTATOR)
299 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
303 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
307 for(i = 0; i < MAX_TEAMSCORE; ++i)
309 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
314 if (left.team < right.team)
320 void Scoreboard_UpdateTeamPos(entity Team)
323 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
325 SORT_SWAP(Team, ent);
327 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
329 SORT_SWAP(ent, Team);
333 void Cmd_Scoreboard_Help()
335 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
336 LOG_HELP(_("Usage:"));
337 LOG_HELP("^2scoreboard_columns_set ^3default");
338 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
339 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
340 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
341 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
342 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
343 LOG_HELP(_("The following field names are recognized (case insensitive):"));
349 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
350 "of game types, then a slash, to make the field show up only in these\n"
351 "or in all but these game types. You can also specify 'all' as a\n"
352 "field to show all fields available for the current game mode."));
355 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
356 "include/exclude ALL teams/noteams game modes."));
359 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
360 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
361 "right of the vertical bar aligned to the right."));
362 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
363 "other gamemodes except DM."));
366 // NOTE: adding a gametype with ? to not warn for an optional field
367 // make sure it's excluded in a previous exclusive rule, if any
368 // otherwise the previous exclusive rule warns anyway
369 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
370 #define SCOREBOARD_DEFAULT_COLUMNS \
371 "ping pl fps name |" \
372 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
373 " -teams,lms/deaths +ft,tdm/deaths" \
375 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
376 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
377 " +tdm,ft,dom,ons,as/teamkills"\
378 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
379 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
380 " +lms/lives +lms/rank" \
381 " +kh/kckills +kh/losses +kh/caps" \
382 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
383 " +as/objectives +nb/faults +nb/goals" \
384 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
385 " +dom/ticks +dom/takes" \
386 " -lms,rc,cts,inv,nb/score"
388 void Cmd_Scoreboard_SetFields(int argc)
393 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
397 return; // do nothing, we don't know gametype and scores yet
399 // sbt_fields uses strunzone on the titles!
400 if(!sbt_field_title[0])
401 for(i = 0; i < MAX_SBT_FIELDS; ++i)
402 sbt_field_title[i] = strzone("(null)");
404 // TODO: re enable with gametype dependant cvars?
405 if(argc < 3) // no arguments provided
406 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
409 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
413 if(argv(2) == "default" || argv(2) == "expand_default")
415 if(argv(2) == "expand_default")
416 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
417 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
419 else if(argv(2) == "all")
421 string s = "ping pl name |"; // scores without a label
422 FOREACH(Scores, true, {
424 if(it != ps_secondary)
425 if(scores_label(it) != "")
426 s = strcat(s, " ", scores_label(it));
428 if(ps_secondary != ps_primary)
429 s = strcat(s, " ", scores_label(ps_secondary));
430 s = strcat(s, " ", scores_label(ps_primary));
431 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
438 hud_fontsize = HUD_GetFontsize("hud_fontsize");
440 for(i = 1; i < argc - 1; ++i)
443 bool nocomplain = false;
444 if(substring(str, 0, 1) == "?")
447 str = substring(str, 1, strlen(str) - 1);
450 slash = strstrofs(str, "/", 0);
453 pattern = substring(str, 0, slash);
454 str = substring(str, slash + 1, strlen(str) - (slash + 1));
456 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
460 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
461 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
462 str = strtolower(str);
467 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
468 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
469 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
470 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
471 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
472 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
473 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
474 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
475 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
476 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
479 FOREACH(Scores, true, {
480 if (str == strtolower(scores_label(it))) {
482 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
492 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
496 sbt_field[sbt_num_fields] = j;
499 if(j == ps_secondary)
500 have_secondary = true;
505 if(sbt_num_fields >= MAX_SBT_FIELDS)
509 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
511 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
512 have_secondary = true;
513 if(ps_primary == ps_secondary)
514 have_secondary = true;
515 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
517 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
521 strunzone(sbt_field_title[sbt_num_fields]);
522 for(i = sbt_num_fields; i > 0; --i)
524 sbt_field_title[i] = sbt_field_title[i-1];
525 sbt_field_size[i] = sbt_field_size[i-1];
526 sbt_field[i] = sbt_field[i-1];
528 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
529 sbt_field[0] = SP_NAME;
531 LOG_INFO("fixed missing field 'name'");
535 strunzone(sbt_field_title[sbt_num_fields]);
536 for(i = sbt_num_fields; i > 1; --i)
538 sbt_field_title[i] = sbt_field_title[i-1];
539 sbt_field_size[i] = sbt_field_size[i-1];
540 sbt_field[i] = sbt_field[i-1];
542 sbt_field_title[1] = strzone("|");
543 sbt_field[1] = SP_SEPARATOR;
544 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
546 LOG_INFO("fixed missing field '|'");
549 else if(!have_separator)
551 strcpy(sbt_field_title[sbt_num_fields], "|");
552 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
553 sbt_field[sbt_num_fields] = SP_SEPARATOR;
555 LOG_INFO("fixed missing field '|'");
559 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
560 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
561 sbt_field[sbt_num_fields] = ps_secondary;
563 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
567 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
568 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
569 sbt_field[sbt_num_fields] = ps_primary;
571 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
575 sbt_field[sbt_num_fields] = SP_END;
579 vector sbt_field_rgb;
580 string sbt_field_icon0;
581 string sbt_field_icon1;
582 string sbt_field_icon2;
583 vector sbt_field_icon0_rgb;
584 vector sbt_field_icon1_rgb;
585 vector sbt_field_icon2_rgb;
586 string Scoreboard_GetName(entity pl)
588 if(ready_waiting && pl.ready)
590 sbt_field_icon0 = "gfx/scoreboard/player_ready";
594 int f = entcs_GetClientColors(pl.sv_entnum);
596 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
597 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
598 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
599 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
600 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
603 return entcs_GetName(pl.sv_entnum);
606 string Scoreboard_GetField(entity pl, PlayerScoreField field)
608 float tmp, num, denom;
611 sbt_field_rgb = '1 1 1';
612 sbt_field_icon0 = "";
613 sbt_field_icon1 = "";
614 sbt_field_icon2 = "";
615 sbt_field_icon0_rgb = '1 1 1';
616 sbt_field_icon1_rgb = '1 1 1';
617 sbt_field_icon2_rgb = '1 1 1';
622 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
623 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
627 tmp = max(0, min(220, f-80)) / 220;
628 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
634 f = pl.ping_packetloss;
635 tmp = pl.ping_movementloss;
636 if(f == 0 && tmp == 0)
638 str = ftos(ceil(f * 100));
640 str = strcat(str, "~", ftos(ceil(tmp * 100)));
641 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
642 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
646 return Scoreboard_GetName(pl);
649 f = pl.(scores(SP_KILLS));
650 f -= pl.(scores(SP_SUICIDES));
654 num = pl.(scores(SP_KILLS));
655 denom = pl.(scores(SP_DEATHS));
658 sbt_field_rgb = '0 1 0';
659 str = sprintf("%d", num);
660 } else if(num <= 0) {
661 sbt_field_rgb = '1 0 0';
662 str = sprintf("%.1f", num/denom);
664 str = sprintf("%.1f", num/denom);
668 f = pl.(scores(SP_KILLS));
669 f -= pl.(scores(SP_DEATHS));
672 sbt_field_rgb = '0 1 0';
674 sbt_field_rgb = '1 1 1';
676 sbt_field_rgb = '1 0 0';
682 float elo = pl.(scores(SP_ELO));
684 case -1: return "...";
685 case -2: return _("N/A");
686 default: return ftos(elo);
692 float fps = pl.(scores(SP_FPS));
695 sbt_field_rgb = '1 1 1';
696 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
698 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
699 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
703 case SP_DMG: case SP_DMGTAKEN:
704 return sprintf("%.1f k", pl.(scores(field)) / 1000);
706 default: case SP_SCORE:
707 tmp = pl.(scores(field));
708 f = scores_flags(field);
709 if(field == ps_primary)
710 sbt_field_rgb = '1 1 0';
711 else if(field == ps_secondary)
712 sbt_field_rgb = '0 1 1';
714 sbt_field_rgb = '1 1 1';
715 return ScoreString(f, tmp);
720 float sbt_fixcolumnwidth_len;
721 float sbt_fixcolumnwidth_iconlen;
722 float sbt_fixcolumnwidth_marginlen;
724 string Scoreboard_FixColumnWidth(int i, string str)
730 sbt_fixcolumnwidth_iconlen = 0;
732 if(sbt_field_icon0 != "")
734 sz = draw_getimagesize(sbt_field_icon0);
736 if(sbt_fixcolumnwidth_iconlen < f)
737 sbt_fixcolumnwidth_iconlen = f;
740 if(sbt_field_icon1 != "")
742 sz = draw_getimagesize(sbt_field_icon1);
744 if(sbt_fixcolumnwidth_iconlen < f)
745 sbt_fixcolumnwidth_iconlen = f;
748 if(sbt_field_icon2 != "")
750 sz = draw_getimagesize(sbt_field_icon2);
752 if(sbt_fixcolumnwidth_iconlen < f)
753 sbt_fixcolumnwidth_iconlen = f;
756 if(sbt_fixcolumnwidth_iconlen != 0)
758 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
759 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
762 sbt_fixcolumnwidth_marginlen = 0;
764 if(sbt_field[i] == SP_NAME) // name gets all remaining space
767 float remaining_space = 0;
768 for(j = 0; j < sbt_num_fields; ++j)
770 if (sbt_field[i] != SP_SEPARATOR)
771 remaining_space += sbt_field_size[j] + hud_fontsize.x;
772 sbt_field_size[i] = panel_size.x - remaining_space;
774 if (sbt_fixcolumnwidth_iconlen != 0)
775 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
776 float namesize = panel_size.x - remaining_space;
777 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
778 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
780 max_namesize = vid_conwidth - remaining_space;
783 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
785 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
786 if(sbt_field_size[i] < f)
787 sbt_field_size[i] = f;
792 void Scoreboard_initFieldSizes()
794 for(int i = 0; i < sbt_num_fields; ++i)
796 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
797 Scoreboard_FixColumnWidth(i, "");
801 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
804 vector column_dim = eY * panel_size.y;
806 column_dim.y -= 1.25 * hud_fontsize.y;
807 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
808 pos.x += hud_fontsize.x * 0.5;
809 for(i = 0; i < sbt_num_fields; ++i)
811 if(sbt_field[i] == SP_SEPARATOR)
813 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
816 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
817 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
818 pos.x += column_dim.x;
820 if(sbt_field[i] == SP_SEPARATOR)
822 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
823 for(i = sbt_num_fields - 1; i > 0; --i)
825 if(sbt_field[i] == SP_SEPARATOR)
828 pos.x -= sbt_field_size[i];
833 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
834 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
837 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
838 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
839 pos.x -= hud_fontsize.x;
844 pos.y += 1.25 * hud_fontsize.y;
848 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
850 TC(bool, is_self); TC(int, pl_number);
852 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
854 vector h_pos = item_pos;
855 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
856 // alternated rows highlighting
858 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
859 else if((sbt_highlight) && (!(pl_number % 2)))
860 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
862 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
864 vector pos = item_pos;
865 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
867 drawstring(pos+eX*(panel_size.x+.5*hud_fontsize.x)+eY, "\xE2\x97\x80", vec2(hud_fontsize.x, hud_fontsize.y), rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
869 pos.x += hud_fontsize.x * 0.5;
870 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
871 vector tmp = '0 0 0';
873 PlayerScoreField field;
874 for(i = 0; i < sbt_num_fields; ++i)
876 field = sbt_field[i];
877 if(field == SP_SEPARATOR)
880 if(is_spec && field != SP_NAME && field != SP_PING) {
881 pos.x += sbt_field_size[i] + hud_fontsize.x;
884 str = Scoreboard_GetField(pl, field);
885 str = Scoreboard_FixColumnWidth(i, str);
887 pos.x += sbt_field_size[i] + hud_fontsize.x;
889 if(field == SP_NAME) {
890 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
891 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
893 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
894 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
897 tmp.x = sbt_field_size[i] + hud_fontsize.x;
898 if(sbt_field_icon0 != "")
899 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
900 if(sbt_field_icon1 != "")
901 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
902 if(sbt_field_icon2 != "")
903 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 if(sbt_field[i] == SP_SEPARATOR)
908 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
909 for(i = sbt_num_fields-1; i > 0; --i)
911 field = sbt_field[i];
912 if(field == SP_SEPARATOR)
915 if(is_spec && field != SP_NAME && field != SP_PING) {
916 pos.x -= sbt_field_size[i] + hud_fontsize.x;
920 str = Scoreboard_GetField(pl, field);
921 str = Scoreboard_FixColumnWidth(i, str);
923 if(field == SP_NAME) {
924 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
925 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
927 tmp.x = sbt_fixcolumnwidth_len;
928 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
931 tmp.x = sbt_field_size[i];
932 if(sbt_field_icon0 != "")
933 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
934 if(sbt_field_icon1 != "")
935 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
936 if(sbt_field_icon2 != "")
937 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
938 pos.x -= sbt_field_size[i] + hud_fontsize.x;
943 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
946 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
949 vector h_pos = item_pos;
950 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
952 bool complete = (this_team == NUM_SPECTATOR);
955 if((sbt_highlight) && (!(pl_number % 2)))
956 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
958 vector pos = item_pos;
959 pos.x += hud_fontsize.x * 0.5;
960 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
962 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
964 width_limit -= stringwidth("...", false, hud_fontsize);
965 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
966 static float max_name_width = 0;
969 float min_fieldsize = 0;
970 float fieldpadding = hud_fontsize.x * 0.25;
971 if(this_team == NUM_SPECTATOR)
973 if(autocvar_hud_panel_scoreboard_spectators_showping)
974 min_fieldsize = stringwidth("999", false, hud_fontsize);
976 else if(autocvar_hud_panel_scoreboard_others_showscore)
977 min_fieldsize = stringwidth("99", false, hud_fontsize);
978 for(i = 0; pl; pl = pl.sort_next)
980 if(pl.team != this_team)
986 if(this_team == NUM_SPECTATOR)
988 if(autocvar_hud_panel_scoreboard_spectators_showping)
989 field = Scoreboard_GetField(pl, SP_PING);
991 else if(autocvar_hud_panel_scoreboard_others_showscore)
992 field = Scoreboard_GetField(pl, SP_SCORE);
994 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
995 float column_width = stringwidth(str, true, hud_fontsize);
996 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
998 if(column_width > max_name_width)
999 max_name_width = column_width;
1000 column_width = max_name_width;
1004 fieldsize = stringwidth(field, false, hud_fontsize);
1005 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1008 if(pos.x + column_width > width_limit)
1013 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1018 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1019 pos.y += hud_fontsize.y * 1.25;
1023 vector name_pos = pos;
1024 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1025 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1026 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1029 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1030 h_size.y = hud_fontsize.y;
1031 vector field_pos = pos;
1032 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1033 field_pos.x += column_width - h_size.x;
1035 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1036 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1037 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1041 h_size.x = column_width + hud_fontsize.x * 0.25;
1042 h_size.y = hud_fontsize.y;
1043 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1045 pos.x += column_width;
1046 pos.x += hud_fontsize.x;
1048 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1051 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1053 int max_players = 999;
1054 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1056 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1059 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1060 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1061 height /= team_count;
1064 height -= panel_bg_padding * 2; // - padding
1065 max_players = floor(height / (hud_fontsize.y * 1.25));
1066 if(max_players <= 1)
1068 if(max_players == tm.team_size)
1073 entity me = playerslots[current_player];
1075 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1076 panel_size.y += panel_bg_padding * 2;
1079 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1080 if(panel.current_panel_bg != "0")
1081 end_pos.y += panel_bg_border * 2;
1083 if(panel_bg_padding)
1085 panel_pos += '1 1 0' * panel_bg_padding;
1086 panel_size -= '2 2 0' * panel_bg_padding;
1090 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1094 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1096 pos.y += 1.25 * hud_fontsize.y;
1099 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1101 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1104 // print header row and highlight columns
1105 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1107 // fill the table and draw the rows
1108 bool is_self = false;
1109 bool self_shown = false;
1111 for(pl = players.sort_next; pl; pl = pl.sort_next)
1113 if(pl.team != tm.team)
1115 if(i == max_players - 2 && pl != me)
1117 if(!self_shown && me.team == tm.team)
1119 Scoreboard_DrawItem(pos, rgb, me, true, i);
1121 pos.y += 1.25 * hud_fontsize.y;
1125 if(i >= max_players - 1)
1127 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1130 is_self = (pl.sv_entnum == current_player);
1131 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1134 pos.y += 1.25 * hud_fontsize.y;
1138 panel_size.x += panel_bg_padding * 2; // restore initial width
1142 bool Scoreboard_WouldDraw()
1144 if (MUTATOR_CALLHOOK(DrawScoreboard))
1146 else if (QuickMenu_IsOpened())
1148 else if (HUD_Radar_Clickable())
1150 else if (scoreboard_showscores)
1152 else if (intermission == 1)
1154 else if (intermission == 2)
1156 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1157 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1161 else if (scoreboard_showscores_force)
1166 float average_accuracy;
1167 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1171 if (scoreboard_fade_alpha == 1)
1172 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1174 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1176 vector initial_pos = pos;
1178 WepSet weapons_stat = WepSet_GetFromStat();
1179 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1180 int disownedcnt = 0;
1182 FOREACH(Weapons, it != WEP_Null, {
1183 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1185 WepSet set = it.m_wepset;
1186 if(it.spawnflags & WEP_TYPE_OTHER)
1191 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1193 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1200 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1201 if (weapon_cnt <= 0) return pos;
1204 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1206 int columnns = ceil(weapon_cnt / rows);
1208 float weapon_height = 29;
1209 float height = hud_fontsize.y + weapon_height;
1211 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);
1212 pos.y += 1.25 * hud_fontsize.y;
1213 if(panel.current_panel_bg != "0")
1214 pos.y += panel_bg_border;
1217 panel_size.y = height * rows;
1218 panel_size.y += panel_bg_padding * 2;
1220 float panel_bg_alpha_save = panel_bg_alpha;
1221 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1223 panel_bg_alpha = panel_bg_alpha_save;
1225 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1226 if(panel.current_panel_bg != "0")
1227 end_pos.y += panel_bg_border * 2;
1229 if(panel_bg_padding)
1231 panel_pos += '1 1 0' * panel_bg_padding;
1232 panel_size -= '2 2 0' * panel_bg_padding;
1236 vector tmp = panel_size;
1238 float weapon_width = tmp.x / columnns / rows;
1241 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1245 // column highlighting
1246 for (int i = 0; i < columnns; ++i)
1248 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);
1251 for (int i = 0; i < rows; ++i)
1252 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1255 average_accuracy = 0;
1256 int weapons_with_stats = 0;
1258 pos.x += weapon_width / 2;
1260 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1263 Accuracy_LoadColors();
1265 float oldposx = pos.x;
1269 FOREACH(Weapons, it != WEP_Null, {
1270 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1272 WepSet set = it.m_wepset;
1273 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1275 if (it.spawnflags & WEP_TYPE_OTHER)
1279 if (weapon_stats >= 0)
1280 weapon_alpha = sbt_fg_alpha;
1282 weapon_alpha = 0.2 * sbt_fg_alpha;
1285 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1287 if (weapon_stats >= 0) {
1288 weapons_with_stats += 1;
1289 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1292 s = sprintf("%d%%", weapon_stats * 100);
1295 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1297 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1298 rgb = Accuracy_GetColor(weapon_stats);
1300 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1302 tmpos.x += weapon_width * rows;
1303 pos.x += weapon_width * rows;
1304 if (rows == 2 && column == columnns - 1) {
1312 if (weapons_with_stats)
1313 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1315 panel_size.x += panel_bg_padding * 2; // restore initial width
1317 if (scoreboard_acc_fade_alpha == 1)
1319 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1322 .bool uninteresting;
1323 STATIC_INIT(default_order_items_label)
1325 IL_EACH(default_order_items, true, {
1333 case "vaporizer_cells":
1336 case "armor_medium":
1337 case "health_small":
1338 case "health_medium":
1339 it.uninteresting = true;
1344 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1346 float scoreboard_acc_fade_alpha_save = scoreboard_acc_fade_alpha; // debug
1347 scoreboard_acc_fade_alpha = 1; // debug: make Item Stats always visible
1349 float initial_posx = pos.x;
1350 int disowned_cnt = 0;
1351 int uninteresting_cnt = 0;
1352 IL_EACH(default_order_items, true, {
1353 int q = g_inventory.inv_items[it.m_id];
1354 //q = 1; // debug: display all items
1355 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1356 ++uninteresting_cnt;
1360 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1361 int n = items_cnt - disowned_cnt;
1362 if (n <= 0) return pos;
1364 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1365 int columnns = max(6, ceil(n / rows));
1368 float fontsize = height * 1/3;
1369 float item_height = height * 2/3;
1371 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1372 pos.y += 1.25 * hud_fontsize.y;
1373 if(panel.current_panel_bg != "0")
1374 pos.y += panel_bg_border;
1377 panel_size.y = height * rows;
1378 panel_size.y += panel_bg_padding * 2;
1380 float panel_bg_alpha_save = panel_bg_alpha;
1381 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1383 panel_bg_alpha = panel_bg_alpha_save;
1385 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1386 if(panel.current_panel_bg != "0")
1387 end_pos.y += panel_bg_border * 2;
1389 if(panel_bg_padding)
1391 panel_pos += '1 1 0' * panel_bg_padding;
1392 panel_size -= '2 2 0' * panel_bg_padding;
1396 vector tmp = panel_size;
1398 float item_width = tmp.x / columnns / rows;
1401 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1405 // column highlighting
1406 for (int i = 0; i < columnns; ++i)
1408 drawfill(pos + '1 0 0' * item_width * rows * i, '0 1 0' * height * rows + '1 0 0' * item_width * rows, '0 0 0', panel_bg_alpha * 0.2, DRAWFLAG_NORMAL);
1411 for (int i = 0; i < rows; ++i)
1412 drawfill(pos + '0 1 0' * item_height + '0 1 0' * height * i, '1 0 0' * panel_size.x + '0 1 0' * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1416 pos.x += item_width / 2;
1418 float oldposx = pos.x;
1422 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1423 int n = g_inventory.inv_items[it.m_id];
1424 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1425 if (n <= 0) continue;
1426 drawpic_aspect_skin(tmpos, it.m_icon, '1 0 0' * item_width + '0 1 0' * item_height, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1428 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1429 drawstring(tmpos + '1 0 0' * padding + '0 1 0' * item_height, s, '1 1 0' * fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1430 tmpos.x += item_width * rows;
1431 pos.x += item_width * rows;
1432 if (rows == 2 && column == columnns - 1) {
1440 pos.y += 1.25 * hud_fontsize.y;
1441 pos.x = initial_posx;
1443 panel_size.x += panel_bg_padding * 2; // restore initial width
1445 scoreboard_acc_fade_alpha = scoreboard_acc_fade_alpha_save; // debug
1449 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1451 pos.x += hud_fontsize.x * 0.25;
1452 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1453 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1454 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1456 pos.y += hud_fontsize.y;
1461 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1462 float stat_secrets_found, stat_secrets_total;
1463 float stat_monsters_killed, stat_monsters_total;
1467 // get monster stats
1468 stat_monsters_killed = STAT(MONSTERS_KILLED);
1469 stat_monsters_total = STAT(MONSTERS_TOTAL);
1471 // get secrets stats
1472 stat_secrets_found = STAT(SECRETS_FOUND);
1473 stat_secrets_total = STAT(SECRETS_TOTAL);
1475 // get number of rows
1476 if(stat_secrets_total)
1478 if(stat_monsters_total)
1481 // if no rows, return
1485 // draw table header
1486 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1487 pos.y += 1.25 * hud_fontsize.y;
1488 if(panel.current_panel_bg != "0")
1489 pos.y += panel_bg_border;
1492 panel_size.y = hud_fontsize.y * rows;
1493 panel_size.y += panel_bg_padding * 2;
1496 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1497 if(panel.current_panel_bg != "0")
1498 end_pos.y += panel_bg_border * 2;
1500 if(panel_bg_padding)
1502 panel_pos += '1 1 0' * panel_bg_padding;
1503 panel_size -= '2 2 0' * panel_bg_padding;
1507 vector tmp = panel_size;
1510 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1513 if(stat_monsters_total)
1515 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1516 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1520 if(stat_secrets_total)
1522 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1523 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1526 panel_size.x += panel_bg_padding * 2; // restore initial width
1531 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1534 RANKINGS_RECEIVED_CNT = 0;
1535 for (i=RANKINGS_CNT-1; i>=0; --i)
1537 ++RANKINGS_RECEIVED_CNT;
1539 if (RANKINGS_RECEIVED_CNT == 0)
1542 vector hl_rgb = rgb + '0.5 0.5 0.5';
1544 pos.y += hud_fontsize.y;
1545 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1546 pos.y += 1.25 * hud_fontsize.y;
1547 if(panel.current_panel_bg != "0")
1548 pos.y += panel_bg_border;
1553 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1555 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1560 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1562 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1566 float ranksize = 3 * hud_fontsize.x;
1567 float timesize = 5 * hud_fontsize.x;
1568 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1569 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1570 columns = min(columns, RANKINGS_RECEIVED_CNT);
1572 // expand name column to fill the entire row
1573 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1574 namesize += available_space;
1575 columnsize.x += available_space;
1577 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1578 panel_size.y += panel_bg_padding * 2;
1582 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1583 if(panel.current_panel_bg != "0")
1584 end_pos.y += panel_bg_border * 2;
1586 if(panel_bg_padding)
1588 panel_pos += '1 1 0' * panel_bg_padding;
1589 panel_size -= '2 2 0' * panel_bg_padding;
1595 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1597 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1599 int column = 0, j = 0;
1600 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1601 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1608 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1609 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1610 else if(!((j + column) & 1) && sbt_highlight)
1611 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1613 str = count_ordinal(i+1);
1614 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1615 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1616 str = ColorTranslateRGB(grecordholder[i]);
1618 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1619 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1621 pos.y += 1.25 * hud_fontsize.y;
1623 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1627 pos.x += panel_size.x / columns;
1628 pos.y = panel_pos.y;
1631 strfree(zoned_name_self);
1633 panel_size.x += panel_bg_padding * 2; // restore initial width
1637 float scoreboard_time;
1638 bool have_weapon_stats;
1639 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1641 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1643 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1646 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1647 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1653 if (!have_weapon_stats)
1655 FOREACH(Weapons, it != WEP_Null, {
1656 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1657 if (weapon_stats >= 0)
1659 have_weapon_stats = true;
1663 if (!have_weapon_stats)
1670 bool have_item_stats;
1671 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1673 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1675 if (!autocvar_hud_panel_scoreboard_itemstats || warmup_stage || ypos > 0.91 * vid_conheight)
1678 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1679 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1685 if (!have_item_stats)
1687 IL_EACH(default_order_items, true, {
1688 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1690 int q = g_inventory.inv_items[it.m_id];
1691 //q = 1; // debug: display all items
1694 have_item_stats = true;
1699 if (!have_item_stats)
1706 void Scoreboard_Draw()
1708 if(!autocvar__hud_configure)
1710 if(!hud_draw_maximized) return;
1712 // frametime checks allow to toggle the scoreboard even when the game is paused
1713 if(scoreboard_active) {
1714 if (scoreboard_fade_alpha < 1)
1715 scoreboard_time = time;
1716 if(hud_configure_menu_open == 1)
1717 scoreboard_fade_alpha = 1;
1718 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1719 if (scoreboard_fadeinspeed && frametime)
1720 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1722 scoreboard_fade_alpha = 1;
1723 if(hud_fontsize_str != autocvar_hud_fontsize)
1725 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1726 Scoreboard_initFieldSizes();
1727 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1731 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1732 if (scoreboard_fadeoutspeed && frametime)
1733 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1735 scoreboard_fade_alpha = 0;
1738 if (!scoreboard_fade_alpha)
1740 scoreboard_acc_fade_alpha = 0;
1745 scoreboard_fade_alpha = 0;
1747 if (autocvar_hud_panel_scoreboard_dynamichud)
1750 HUD_Scale_Disable();
1752 if(scoreboard_fade_alpha <= 0)
1754 panel_fade_alpha *= scoreboard_fade_alpha;
1755 HUD_Panel_LoadCvars();
1757 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1758 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1759 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1760 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1761 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1762 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1764 // don't overlap with con_notify
1765 if(!autocvar__hud_configure)
1766 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1768 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1769 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1770 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1771 panel_size.x = fixed_scoreboard_width;
1773 Scoreboard_UpdatePlayerTeams();
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 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;