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, {
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)
1348 if (scoreboard_fade_alpha < 1)
1349 scoreboard_itemstats_fade_alpha = min(1, scoreboard_itemstats_fade_alpha + frametime * 10);
1351 scoreboard_itemstats_fade_alpha = 1; // sync fading with the scoreboard
1353 vector initial_pos = pos;
1355 int disowned_cnt = 0;
1356 int uninteresting_cnt = 0;
1357 IL_EACH(default_order_items, true, {
1358 int q = g_inventory.inv_items[it.m_id];
1359 //q = 1; // debug: display all items
1360 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1361 ++uninteresting_cnt;
1365 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1366 int n = items_cnt - disowned_cnt;
1367 if (n <= 0) return pos;
1369 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1370 int columnns = max(6, ceil(n / rows));
1373 float fontsize = height * 1/3;
1374 float item_height = height * 2/3;
1376 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1377 pos.y += 1.25 * hud_fontsize.y;
1378 if(panel.current_panel_bg != "0")
1379 pos.y += panel_bg_border;
1382 panel_size.y = height * rows;
1383 panel_size.y += panel_bg_padding * 2;
1385 float panel_bg_alpha_save = panel_bg_alpha;
1386 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1388 panel_bg_alpha = panel_bg_alpha_save;
1390 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1391 if(panel.current_panel_bg != "0")
1392 end_pos.y += panel_bg_border * 2;
1394 if(panel_bg_padding)
1396 panel_pos += '1 1 0' * panel_bg_padding;
1397 panel_size -= '2 2 0' * panel_bg_padding;
1401 vector tmp = panel_size;
1403 float item_width = tmp.x / columnns / rows;
1406 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1410 // column highlighting
1411 for (int i = 0; i < columnns; ++i)
1413 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);
1416 for (int i = 0; i < rows; ++i)
1417 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);
1421 pos.x += item_width / 2;
1423 float oldposx = pos.x;
1427 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1428 int n = g_inventory.inv_items[it.m_id];
1429 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1430 if (n <= 0) continue;
1431 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);
1433 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1434 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);
1435 tmpos.x += item_width * rows;
1436 pos.x += item_width * rows;
1437 if (rows == 2 && column == columnns - 1) {
1445 panel_size.x += panel_bg_padding * 2; // restore initial width
1447 if (scoreboard_itemstats_fade_alpha == 1)
1449 return initial_pos + (end_pos - initial_pos) * scoreboard_itemstats_fade_alpha;
1452 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1454 pos.x += hud_fontsize.x * 0.25;
1455 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1456 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1457 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1459 pos.y += hud_fontsize.y;
1464 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1465 float stat_secrets_found, stat_secrets_total;
1466 float stat_monsters_killed, stat_monsters_total;
1470 // get monster stats
1471 stat_monsters_killed = STAT(MONSTERS_KILLED);
1472 stat_monsters_total = STAT(MONSTERS_TOTAL);
1474 // get secrets stats
1475 stat_secrets_found = STAT(SECRETS_FOUND);
1476 stat_secrets_total = STAT(SECRETS_TOTAL);
1478 // get number of rows
1479 if(stat_secrets_total)
1481 if(stat_monsters_total)
1484 // if no rows, return
1488 // draw table header
1489 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1490 pos.y += 1.25 * hud_fontsize.y;
1491 if(panel.current_panel_bg != "0")
1492 pos.y += panel_bg_border;
1495 panel_size.y = hud_fontsize.y * rows;
1496 panel_size.y += panel_bg_padding * 2;
1499 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1500 if(panel.current_panel_bg != "0")
1501 end_pos.y += panel_bg_border * 2;
1503 if(panel_bg_padding)
1505 panel_pos += '1 1 0' * panel_bg_padding;
1506 panel_size -= '2 2 0' * panel_bg_padding;
1510 vector tmp = panel_size;
1513 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1516 if(stat_monsters_total)
1518 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1519 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1523 if(stat_secrets_total)
1525 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1526 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1529 panel_size.x += panel_bg_padding * 2; // restore initial width
1534 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1537 RANKINGS_RECEIVED_CNT = 0;
1538 for (i=RANKINGS_CNT-1; i>=0; --i)
1540 ++RANKINGS_RECEIVED_CNT;
1542 if (RANKINGS_RECEIVED_CNT == 0)
1545 vector hl_rgb = rgb + '0.5 0.5 0.5';
1547 pos.y += hud_fontsize.y;
1548 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1549 pos.y += 1.25 * hud_fontsize.y;
1550 if(panel.current_panel_bg != "0")
1551 pos.y += panel_bg_border;
1556 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1558 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1563 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1565 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1569 float ranksize = 3 * hud_fontsize.x;
1570 float timesize = 5 * hud_fontsize.x;
1571 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1572 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1573 columns = min(columns, RANKINGS_RECEIVED_CNT);
1575 // expand name column to fill the entire row
1576 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1577 namesize += available_space;
1578 columnsize.x += available_space;
1580 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1581 panel_size.y += panel_bg_padding * 2;
1585 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1586 if(panel.current_panel_bg != "0")
1587 end_pos.y += panel_bg_border * 2;
1589 if(panel_bg_padding)
1591 panel_pos += '1 1 0' * panel_bg_padding;
1592 panel_size -= '2 2 0' * panel_bg_padding;
1598 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1600 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1602 int column = 0, j = 0;
1603 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1604 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1611 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1612 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1613 else if(!((j + column) & 1) && sbt_highlight)
1614 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1616 str = count_ordinal(i+1);
1617 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1618 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1619 str = ColorTranslateRGB(grecordholder[i]);
1621 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1622 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1624 pos.y += 1.25 * hud_fontsize.y;
1626 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1630 pos.x += panel_size.x / columns;
1631 pos.y = panel_pos.y;
1634 strfree(zoned_name_self);
1636 panel_size.x += panel_bg_padding * 2; // restore initial width
1640 float scoreboard_time;
1641 bool have_weapon_stats;
1642 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1644 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1646 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1649 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1650 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1656 if (!have_weapon_stats)
1658 FOREACH(Weapons, it != WEP_Null, {
1659 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1660 if (weapon_stats >= 0)
1662 have_weapon_stats = true;
1666 if (!have_weapon_stats)
1673 bool have_item_stats;
1674 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1676 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1678 if (!autocvar_hud_panel_scoreboard_itemstats || warmup_stage || ypos > 0.91 * vid_conheight)
1681 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1682 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1688 if (!have_item_stats)
1690 IL_EACH(default_order_items, true, {
1691 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1693 int q = g_inventory.inv_items[it.m_id];
1694 //q = 1; // debug: display all items
1697 have_item_stats = true;
1702 if (!have_item_stats)
1709 void Scoreboard_Draw()
1711 if(!autocvar__hud_configure)
1713 if(!hud_draw_maximized) return;
1715 // frametime checks allow to toggle the scoreboard even when the game is paused
1716 if(scoreboard_active) {
1717 if (scoreboard_fade_alpha < 1)
1718 scoreboard_time = time;
1719 if(hud_configure_menu_open == 1)
1720 scoreboard_fade_alpha = 1;
1721 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1722 if (scoreboard_fadeinspeed && frametime)
1723 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1725 scoreboard_fade_alpha = 1;
1726 if(hud_fontsize_str != autocvar_hud_fontsize)
1728 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1729 Scoreboard_initFieldSizes();
1730 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1734 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1735 if (scoreboard_fadeoutspeed && frametime)
1736 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1738 scoreboard_fade_alpha = 0;
1741 if (!scoreboard_fade_alpha)
1743 scoreboard_acc_fade_alpha = 0;
1744 scoreboard_itemstats_fade_alpha = 0;
1749 scoreboard_fade_alpha = 0;
1751 if (autocvar_hud_panel_scoreboard_dynamichud)
1754 HUD_Scale_Disable();
1756 if(scoreboard_fade_alpha <= 0)
1758 panel_fade_alpha *= scoreboard_fade_alpha;
1759 HUD_Panel_LoadCvars();
1761 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1762 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1763 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1764 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1765 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1766 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1768 // don't overlap with con_notify
1769 if(!autocvar__hud_configure)
1770 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1772 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1773 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1774 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1775 panel_size.x = fixed_scoreboard_width;
1777 Scoreboard_UpdatePlayerTeams();
1779 vector pos = panel_pos;
1784 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1786 // Begin of Game Info Section
1787 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1788 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1790 // Game Info: Game Type
1791 str = MapInfo_Type_ToText(gametype);
1792 draw_beginBoldFont();
1793 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);
1796 // Game Info: Game Detail
1797 float tl = STAT(TIMELIMIT);
1798 float fl = STAT(FRAGLIMIT);
1799 float ll = STAT(LEADLIMIT);
1800 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1803 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1804 if(!gametype.m_hidelimits)
1809 str = strcat(str, "^7 / "); // delimiter
1812 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1813 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1814 (teamscores_label(ts_primary) == "fastest") ? "" :
1815 TranslateScoresLabel(teamscores_label(ts_primary))));
1819 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1820 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1821 (scores_label(ps_primary) == "fastest") ? "" :
1822 TranslateScoresLabel(scores_label(ps_primary))));
1827 if(tl > 0 || fl > 0)
1830 if (ll_and_fl && fl > 0)
1831 str = strcat(str, "^7 & ");
1833 str = strcat(str, "^7 / ");
1838 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1839 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1840 (teamscores_label(ts_primary) == "fastest") ? "" :
1841 TranslateScoresLabel(teamscores_label(ts_primary))));
1845 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1846 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1847 (scores_label(ps_primary) == "fastest") ? "" :
1848 TranslateScoresLabel(scores_label(ps_primary))));
1853 pos.y += sb_gameinfo_type_fontsize.y;
1854 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
1856 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1857 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1858 // End of Game Info Section
1860 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1861 if(panel.current_panel_bg != "0")
1862 pos.y += panel_bg_border;
1864 // Draw the scoreboard
1865 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1868 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1872 vector panel_bg_color_save = panel_bg_color;
1873 vector team_score_baseoffset;
1874 vector team_size_baseoffset;
1875 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1877 // put team score to the left of scoreboard (and team size to the right)
1878 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1879 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1880 if(panel.current_panel_bg != "0")
1882 team_score_baseoffset.x -= panel_bg_border;
1883 team_size_baseoffset.x += panel_bg_border;
1888 // put team score to the right of scoreboard (and team size to the left)
1889 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1890 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1891 if(panel.current_panel_bg != "0")
1893 team_score_baseoffset.x += panel_bg_border;
1894 team_size_baseoffset.x -= panel_bg_border;
1898 int team_size_total = 0;
1899 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1901 // calculate team size total (sum of all team sizes)
1902 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1903 if(tm.team != NUM_SPECTATOR)
1904 team_size_total += tm.team_size;
1907 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1909 if(tm.team == NUM_SPECTATOR)
1914 draw_beginBoldFont();
1915 vector rgb = Team_ColorRGB(tm.team);
1916 str = ftos(tm.(teamscores(ts_primary)));
1917 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1919 // team score on the left (default)
1920 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1924 // team score on the right
1925 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1927 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1929 // team size (if set to show on the side)
1930 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1932 // calculate the starting position for the whole team size info string
1933 str = sprintf("%d/%d", tm.team_size, team_size_total);
1934 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1936 // team size on the left
1937 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1941 // team size on the right
1942 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1944 str = sprintf("%d", tm.team_size);
1945 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1946 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1947 str = sprintf("/%d", team_size_total);
1948 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1952 // secondary score, e.g. keyhunt
1953 if(ts_primary != ts_secondary)
1955 str = ftos(tm.(teamscores(ts_secondary)));
1956 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1959 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1964 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1967 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1970 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1971 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1972 else if(panel_bg_color_team > 0)
1973 panel_bg_color = rgb * panel_bg_color_team;
1975 panel_bg_color = rgb;
1976 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1978 panel_bg_color = panel_bg_color_save;
1982 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1983 if(tm.team != NUM_SPECTATOR)
1986 // display it anyway
1987 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1990 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1991 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1992 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1993 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1995 if(MUTATOR_CALLHOOK(ShowRankings)) {
1996 string ranktitle = M_ARGV(0, string);
1997 if(race_speedaward) {
1998 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);
1999 pos.y += 1.25 * hud_fontsize.y;
2001 if(race_speedaward_alltimebest) {
2002 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);
2003 pos.y += 1.25 * hud_fontsize.y;
2005 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2008 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2011 for(pl = players.sort_next; pl; pl = pl.sort_next)
2013 if(pl.team == NUM_SPECTATOR)
2015 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2016 if(tm.team == NUM_SPECTATOR)
2018 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2019 draw_beginBoldFont();
2020 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2022 pos.y += 1.25 * hud_fontsize.y;
2024 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2025 pos.y += 1.25 * hud_fontsize.y;
2032 // print information about respawn status
2033 float respawn_time = STAT(RESPAWN_TIME);
2037 if(respawn_time < 0)
2039 // a negative number means we are awaiting respawn, time value is still the same
2040 respawn_time *= -1; // remove mark now that we checked it
2042 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2043 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2045 str = sprintf(_("^1Respawning in ^3%s^1..."),
2046 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2047 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2049 count_seconds(ceil(respawn_time - time))
2053 else if(time < respawn_time)
2055 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2056 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2057 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2059 count_seconds(ceil(respawn_time - time))
2063 else if(time >= respawn_time)
2064 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2066 pos.y += 1.2 * hud_fontsize.y;
2067 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2070 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;