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_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 || MUTATOR_CALLHOOK(DrawScoreboard_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, {
1326 if(!(it.instanceOfPowerup
1327 || it == ITEM_HealthMega || it == ITEM_HealthBig
1328 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1331 it.uninteresting = true;
1336 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1340 if (scoreboard_fade_alpha < 1)
1341 scoreboard_itemstats_fade_alpha = min(1, scoreboard_itemstats_fade_alpha + frametime * 10);
1343 scoreboard_itemstats_fade_alpha = 1; // sync fading with the scoreboard
1345 vector initial_pos = pos;
1347 int disowned_cnt = 0;
1348 int uninteresting_cnt = 0;
1349 IL_EACH(default_order_items, true, {
1350 int q = g_inventory.inv_items[it.m_id];
1351 //q = 1; // debug: display all items
1352 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1353 ++uninteresting_cnt;
1357 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1358 int n = items_cnt - disowned_cnt;
1359 if (n <= 0) return pos;
1361 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1362 int columnns = max(6, ceil(n / rows));
1365 float fontsize = height * 1/3;
1366 float item_height = height * 2/3;
1368 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1369 pos.y += 1.25 * hud_fontsize.y;
1370 if(panel.current_panel_bg != "0")
1371 pos.y += panel_bg_border;
1374 panel_size.y = height * rows;
1375 panel_size.y += panel_bg_padding * 2;
1377 float panel_bg_alpha_save = panel_bg_alpha;
1378 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1380 panel_bg_alpha = panel_bg_alpha_save;
1382 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1383 if(panel.current_panel_bg != "0")
1384 end_pos.y += panel_bg_border * 2;
1386 if(panel_bg_padding)
1388 panel_pos += '1 1 0' * panel_bg_padding;
1389 panel_size -= '2 2 0' * panel_bg_padding;
1393 vector tmp = panel_size;
1395 float item_width = tmp.x / columnns / rows;
1398 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1402 // column highlighting
1403 for (int i = 0; i < columnns; ++i)
1405 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);
1408 for (int i = 0; i < rows; ++i)
1409 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);
1413 pos.x += item_width / 2;
1415 float oldposx = pos.x;
1419 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1420 int n = g_inventory.inv_items[it.m_id];
1421 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1422 if (n <= 0) continue;
1423 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);
1425 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1426 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);
1427 tmpos.x += item_width * rows;
1428 pos.x += item_width * rows;
1429 if (rows == 2 && column == columnns - 1) {
1437 panel_size.x += panel_bg_padding * 2; // restore initial width
1439 if (scoreboard_itemstats_fade_alpha == 1)
1441 return initial_pos + (end_pos - initial_pos) * scoreboard_itemstats_fade_alpha;
1444 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1446 pos.x += hud_fontsize.x * 0.25;
1447 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1448 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1449 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1451 pos.y += hud_fontsize.y;
1456 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1457 float stat_secrets_found, stat_secrets_total;
1458 float stat_monsters_killed, stat_monsters_total;
1462 // get monster stats
1463 stat_monsters_killed = STAT(MONSTERS_KILLED);
1464 stat_monsters_total = STAT(MONSTERS_TOTAL);
1466 // get secrets stats
1467 stat_secrets_found = STAT(SECRETS_FOUND);
1468 stat_secrets_total = STAT(SECRETS_TOTAL);
1470 // get number of rows
1471 if(stat_secrets_total)
1473 if(stat_monsters_total)
1476 // if no rows, return
1480 // draw table header
1481 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1482 pos.y += 1.25 * hud_fontsize.y;
1483 if(panel.current_panel_bg != "0")
1484 pos.y += panel_bg_border;
1487 panel_size.y = hud_fontsize.y * rows;
1488 panel_size.y += panel_bg_padding * 2;
1491 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1492 if(panel.current_panel_bg != "0")
1493 end_pos.y += panel_bg_border * 2;
1495 if(panel_bg_padding)
1497 panel_pos += '1 1 0' * panel_bg_padding;
1498 panel_size -= '2 2 0' * panel_bg_padding;
1502 vector tmp = panel_size;
1505 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1508 if(stat_monsters_total)
1510 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1511 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1515 if(stat_secrets_total)
1517 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1518 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1521 panel_size.x += panel_bg_padding * 2; // restore initial width
1526 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1529 RANKINGS_RECEIVED_CNT = 0;
1530 for (i=RANKINGS_CNT-1; i>=0; --i)
1532 ++RANKINGS_RECEIVED_CNT;
1534 if (RANKINGS_RECEIVED_CNT == 0)
1537 vector hl_rgb = rgb + '0.5 0.5 0.5';
1539 pos.y += hud_fontsize.y;
1540 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1541 pos.y += 1.25 * hud_fontsize.y;
1542 if(panel.current_panel_bg != "0")
1543 pos.y += panel_bg_border;
1548 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1550 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1555 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1557 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1561 float ranksize = 3 * hud_fontsize.x;
1562 float timesize = 5 * hud_fontsize.x;
1563 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1564 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1565 columns = min(columns, RANKINGS_RECEIVED_CNT);
1567 // expand name column to fill the entire row
1568 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1569 namesize += available_space;
1570 columnsize.x += available_space;
1572 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1573 panel_size.y += panel_bg_padding * 2;
1577 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1578 if(panel.current_panel_bg != "0")
1579 end_pos.y += panel_bg_border * 2;
1581 if(panel_bg_padding)
1583 panel_pos += '1 1 0' * panel_bg_padding;
1584 panel_size -= '2 2 0' * panel_bg_padding;
1590 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1592 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1594 int column = 0, j = 0;
1595 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1596 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1603 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1604 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1605 else if(!((j + column) & 1) && sbt_highlight)
1606 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1608 str = count_ordinal(i+1);
1609 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1610 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1611 str = ColorTranslateRGB(grecordholder[i]);
1613 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1614 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1616 pos.y += 1.25 * hud_fontsize.y;
1618 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1622 pos.x += panel_size.x / columns;
1623 pos.y = panel_pos.y;
1626 strfree(zoned_name_self);
1628 panel_size.x += panel_bg_padding * 2; // restore initial width
1632 float scoreboard_time;
1633 bool have_weapon_stats;
1634 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1636 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1638 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1641 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1642 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1648 if (!have_weapon_stats)
1650 FOREACH(Weapons, it != WEP_Null, {
1651 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1652 if (weapon_stats >= 0)
1654 have_weapon_stats = true;
1658 if (!have_weapon_stats)
1665 bool have_item_stats;
1666 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1668 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1670 if (!autocvar_hud_panel_scoreboard_itemstats || warmup_stage || ypos > 0.91 * vid_conheight)
1673 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1674 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1680 if (!have_item_stats)
1682 IL_EACH(default_order_items, true, {
1683 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1685 int q = g_inventory.inv_items[it.m_id];
1686 //q = 1; // debug: display all items
1689 have_item_stats = true;
1694 if (!have_item_stats)
1701 void Scoreboard_Draw()
1703 if(!autocvar__hud_configure)
1705 if(!hud_draw_maximized) return;
1707 // frametime checks allow to toggle the scoreboard even when the game is paused
1708 if(scoreboard_active) {
1709 if (scoreboard_fade_alpha < 1)
1710 scoreboard_time = time;
1711 if(hud_configure_menu_open == 1)
1712 scoreboard_fade_alpha = 1;
1713 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1714 if (scoreboard_fadeinspeed && frametime)
1715 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1717 scoreboard_fade_alpha = 1;
1718 if(hud_fontsize_str != autocvar_hud_fontsize)
1720 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1721 Scoreboard_initFieldSizes();
1722 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1726 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1727 if (scoreboard_fadeoutspeed && frametime)
1728 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1730 scoreboard_fade_alpha = 0;
1733 if (!scoreboard_fade_alpha)
1735 scoreboard_acc_fade_alpha = 0;
1736 scoreboard_itemstats_fade_alpha = 0;
1741 scoreboard_fade_alpha = 0;
1743 if (autocvar_hud_panel_scoreboard_dynamichud)
1746 HUD_Scale_Disable();
1748 if(scoreboard_fade_alpha <= 0)
1750 panel_fade_alpha *= scoreboard_fade_alpha;
1751 HUD_Panel_LoadCvars();
1753 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1754 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1755 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1756 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1757 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1758 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1760 // don't overlap with con_notify
1761 if(!autocvar__hud_configure)
1762 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1764 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1765 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1766 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1767 panel_size.x = fixed_scoreboard_width;
1769 Scoreboard_UpdatePlayerTeams();
1771 vector pos = panel_pos;
1776 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1778 // Begin of Game Info Section
1779 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1780 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1782 // Game Info: Game Type
1783 str = MapInfo_Type_ToText(gametype);
1784 draw_beginBoldFont();
1785 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);
1788 // Game Info: Game Detail
1789 float tl = STAT(TIMELIMIT);
1790 float fl = STAT(FRAGLIMIT);
1791 float ll = STAT(LEADLIMIT);
1792 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1795 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1796 if(!gametype.m_hidelimits)
1801 str = strcat(str, "^7 / "); // delimiter
1804 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1805 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1806 (teamscores_label(ts_primary) == "fastest") ? "" :
1807 TranslateScoresLabel(teamscores_label(ts_primary))));
1811 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1812 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1813 (scores_label(ps_primary) == "fastest") ? "" :
1814 TranslateScoresLabel(scores_label(ps_primary))));
1819 if(tl > 0 || fl > 0)
1822 if (ll_and_fl && fl > 0)
1823 str = strcat(str, "^7 & ");
1825 str = strcat(str, "^7 / ");
1830 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1831 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1832 (teamscores_label(ts_primary) == "fastest") ? "" :
1833 TranslateScoresLabel(teamscores_label(ts_primary))));
1837 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1838 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1839 (scores_label(ps_primary) == "fastest") ? "" :
1840 TranslateScoresLabel(scores_label(ps_primary))));
1845 pos.y += sb_gameinfo_type_fontsize.y;
1846 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
1848 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1849 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1850 // End of Game Info Section
1852 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1853 if(panel.current_panel_bg != "0")
1854 pos.y += panel_bg_border;
1856 // Draw the scoreboard
1857 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1860 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1864 vector panel_bg_color_save = panel_bg_color;
1865 vector team_score_baseoffset;
1866 vector team_size_baseoffset;
1867 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1869 // put team score to the left of scoreboard (and team size to the right)
1870 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1871 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1872 if(panel.current_panel_bg != "0")
1874 team_score_baseoffset.x -= panel_bg_border;
1875 team_size_baseoffset.x += panel_bg_border;
1880 // put team score to the right of scoreboard (and team size to the left)
1881 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1882 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1883 if(panel.current_panel_bg != "0")
1885 team_score_baseoffset.x += panel_bg_border;
1886 team_size_baseoffset.x -= panel_bg_border;
1890 int team_size_total = 0;
1891 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1893 // calculate team size total (sum of all team sizes)
1894 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1895 if(tm.team != NUM_SPECTATOR)
1896 team_size_total += tm.team_size;
1899 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1901 if(tm.team == NUM_SPECTATOR)
1906 draw_beginBoldFont();
1907 vector rgb = Team_ColorRGB(tm.team);
1908 str = ftos(tm.(teamscores(ts_primary)));
1909 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1911 // team score on the left (default)
1912 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1916 // team score on the right
1917 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1919 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1921 // team size (if set to show on the side)
1922 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1924 // calculate the starting position for the whole team size info string
1925 str = sprintf("%d/%d", tm.team_size, team_size_total);
1926 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1928 // team size on the left
1929 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1933 // team size on the right
1934 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1936 str = sprintf("%d", tm.team_size);
1937 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1938 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1939 str = sprintf("/%d", team_size_total);
1940 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1944 // secondary score, e.g. keyhunt
1945 if(ts_primary != ts_secondary)
1947 str = ftos(tm.(teamscores(ts_secondary)));
1948 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1951 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1956 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1959 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1962 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1963 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1964 else if(panel_bg_color_team > 0)
1965 panel_bg_color = rgb * panel_bg_color_team;
1967 panel_bg_color = rgb;
1968 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1970 panel_bg_color = panel_bg_color_save;
1974 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1975 if(tm.team != NUM_SPECTATOR)
1978 // display it anyway
1979 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1982 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1983 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1984 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1985 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1987 if(MUTATOR_CALLHOOK(ShowRankings)) {
1988 string ranktitle = M_ARGV(0, string);
1989 if(race_speedaward) {
1990 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);
1991 pos.y += 1.25 * hud_fontsize.y;
1993 if(race_speedaward_alltimebest) {
1994 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);
1995 pos.y += 1.25 * hud_fontsize.y;
1997 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2000 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2003 for(pl = players.sort_next; pl; pl = pl.sort_next)
2005 if(pl.team == NUM_SPECTATOR)
2007 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2008 if(tm.team == NUM_SPECTATOR)
2010 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2011 draw_beginBoldFont();
2012 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2014 pos.y += 1.25 * hud_fontsize.y;
2016 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2017 pos.y += 1.25 * hud_fontsize.y;
2024 // print information about respawn status
2025 float respawn_time = STAT(RESPAWN_TIME);
2029 if(respawn_time < 0)
2031 // a negative number means we are awaiting respawn, time value is still the same
2032 respawn_time *= -1; // remove mark now that we checked it
2034 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2035 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2037 str = sprintf(_("^1Respawning in ^3%s^1..."),
2038 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2039 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2041 count_seconds(ceil(respawn_time - time))
2045 else if(time < respawn_time)
2047 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2048 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2049 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2051 count_seconds(ceil(respawn_time - time))
2055 else if(time >= respawn_time)
2056 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2058 pos.y += 1.2 * hud_fontsize.y;
2059 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2062 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;