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)
1169 scoreboard_acc_fade_alpha = min(scoreboard_fade_alpha, scoreboard_acc_fade_alpha + frametime * 10);
1170 vector initial_pos = pos;
1172 WepSet weapons_stat = WepSet_GetFromStat();
1173 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1174 int disownedcnt = 0;
1176 FOREACH(Weapons, it != WEP_Null, {
1177 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1179 WepSet set = it.m_wepset;
1180 if(it.spawnflags & WEP_TYPE_OTHER)
1185 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1187 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1194 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1195 if (weapon_cnt <= 0) return pos;
1198 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1200 int columnns = ceil(weapon_cnt / rows);
1202 float weapon_height = 29;
1203 float height = hud_fontsize.y + weapon_height;
1205 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);
1206 pos.y += 1.25 * hud_fontsize.y;
1207 if(panel.current_panel_bg != "0")
1208 pos.y += panel_bg_border;
1211 panel_size.y = height * rows;
1212 panel_size.y += panel_bg_padding * 2;
1214 float panel_bg_alpha_save = panel_bg_alpha;
1215 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1217 panel_bg_alpha = panel_bg_alpha_save;
1219 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1220 if(panel.current_panel_bg != "0")
1221 end_pos.y += panel_bg_border * 2;
1223 if(panel_bg_padding)
1225 panel_pos += '1 1 0' * panel_bg_padding;
1226 panel_size -= '2 2 0' * panel_bg_padding;
1230 vector tmp = panel_size;
1232 float weapon_width = tmp.x / columnns / rows;
1235 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1239 // column highlighting
1240 for (int i = 0; i < columnns; ++i)
1242 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);
1245 for (int i = 0; i < rows; ++i)
1246 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1249 average_accuracy = 0;
1250 int weapons_with_stats = 0;
1252 pos.x += weapon_width / 2;
1254 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1257 Accuracy_LoadColors();
1259 float oldposx = pos.x;
1263 FOREACH(Weapons, it != WEP_Null, {
1264 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1266 WepSet set = it.m_wepset;
1267 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1269 if (it.spawnflags & WEP_TYPE_OTHER)
1273 if (weapon_stats >= 0)
1274 weapon_alpha = sbt_fg_alpha;
1276 weapon_alpha = 0.2 * sbt_fg_alpha;
1279 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1281 if (weapon_stats >= 0) {
1282 weapons_with_stats += 1;
1283 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1286 s = sprintf("%d%%", weapon_stats * 100);
1289 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1291 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1292 rgb = Accuracy_GetColor(weapon_stats);
1294 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1296 tmpos.x += weapon_width * rows;
1297 pos.x += weapon_width * rows;
1298 if (rows == 2 && column == columnns - 1) {
1306 if (weapons_with_stats)
1307 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1309 panel_size.x += panel_bg_padding * 2; // restore initial width
1311 if (scoreboard_acc_fade_alpha == 1)
1313 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1316 .bool uninteresting;
1317 STATIC_INIT(default_order_items_label)
1319 IL_EACH(default_order_items, true, {
1320 if(!(it.instanceOfPowerup
1321 || it == ITEM_HealthMega || it == ITEM_HealthBig
1322 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1325 it.uninteresting = true;
1330 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1332 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1333 vector initial_pos = pos;
1335 int disowned_cnt = 0;
1336 int uninteresting_cnt = 0;
1337 IL_EACH(default_order_items, true, {
1338 int q = g_inventory.inv_items[it.m_id];
1339 //q = 1; // debug: display all items
1340 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1341 ++uninteresting_cnt;
1345 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1346 int n = items_cnt - disowned_cnt;
1347 if (n <= 0) return pos;
1349 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1350 int columnns = max(6, ceil(n / rows));
1353 float fontsize = height * 1/3;
1354 float item_height = height * 2/3;
1356 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1357 pos.y += 1.25 * hud_fontsize.y;
1358 if(panel.current_panel_bg != "0")
1359 pos.y += panel_bg_border;
1362 panel_size.y = height * rows;
1363 panel_size.y += panel_bg_padding * 2;
1365 float panel_bg_alpha_save = panel_bg_alpha;
1366 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1368 panel_bg_alpha = panel_bg_alpha_save;
1370 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1371 if(panel.current_panel_bg != "0")
1372 end_pos.y += panel_bg_border * 2;
1374 if(panel_bg_padding)
1376 panel_pos += '1 1 0' * panel_bg_padding;
1377 panel_size -= '2 2 0' * panel_bg_padding;
1381 vector tmp = panel_size;
1383 float item_width = tmp.x / columnns / rows;
1386 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1390 // column highlighting
1391 for (int i = 0; i < columnns; ++i)
1393 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);
1396 for (int i = 0; i < rows; ++i)
1397 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);
1401 pos.x += item_width / 2;
1403 float oldposx = pos.x;
1407 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1408 int n = g_inventory.inv_items[it.m_id];
1409 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1410 if (n <= 0) continue;
1411 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);
1413 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1414 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);
1415 tmpos.x += item_width * rows;
1416 pos.x += item_width * rows;
1417 if (rows == 2 && column == columnns - 1) {
1425 panel_size.x += panel_bg_padding * 2; // restore initial width
1427 if (scoreboard_itemstats_fade_alpha == 1)
1429 return initial_pos + (end_pos - initial_pos) * scoreboard_itemstats_fade_alpha;
1432 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1434 pos.x += hud_fontsize.x * 0.25;
1435 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1436 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1437 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1439 pos.y += hud_fontsize.y;
1444 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1445 float stat_secrets_found, stat_secrets_total;
1446 float stat_monsters_killed, stat_monsters_total;
1450 // get monster stats
1451 stat_monsters_killed = STAT(MONSTERS_KILLED);
1452 stat_monsters_total = STAT(MONSTERS_TOTAL);
1454 // get secrets stats
1455 stat_secrets_found = STAT(SECRETS_FOUND);
1456 stat_secrets_total = STAT(SECRETS_TOTAL);
1458 // get number of rows
1459 if(stat_secrets_total)
1461 if(stat_monsters_total)
1464 // if no rows, return
1468 // draw table header
1469 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1470 pos.y += 1.25 * hud_fontsize.y;
1471 if(panel.current_panel_bg != "0")
1472 pos.y += panel_bg_border;
1475 panel_size.y = hud_fontsize.y * rows;
1476 panel_size.y += panel_bg_padding * 2;
1479 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1480 if(panel.current_panel_bg != "0")
1481 end_pos.y += panel_bg_border * 2;
1483 if(panel_bg_padding)
1485 panel_pos += '1 1 0' * panel_bg_padding;
1486 panel_size -= '2 2 0' * panel_bg_padding;
1490 vector tmp = panel_size;
1493 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1496 if(stat_monsters_total)
1498 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1499 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1503 if(stat_secrets_total)
1505 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1506 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1509 panel_size.x += panel_bg_padding * 2; // restore initial width
1514 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1517 RANKINGS_RECEIVED_CNT = 0;
1518 for (i=RANKINGS_CNT-1; i>=0; --i)
1520 ++RANKINGS_RECEIVED_CNT;
1522 if (RANKINGS_RECEIVED_CNT == 0)
1525 vector hl_rgb = rgb + '0.5 0.5 0.5';
1527 pos.y += hud_fontsize.y;
1528 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1529 pos.y += 1.25 * hud_fontsize.y;
1530 if(panel.current_panel_bg != "0")
1531 pos.y += panel_bg_border;
1536 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1538 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1543 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1545 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1549 float ranksize = 3 * hud_fontsize.x;
1550 float timesize = 5 * hud_fontsize.x;
1551 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1552 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1553 columns = min(columns, RANKINGS_RECEIVED_CNT);
1555 // expand name column to fill the entire row
1556 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1557 namesize += available_space;
1558 columnsize.x += available_space;
1560 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1561 panel_size.y += panel_bg_padding * 2;
1565 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1566 if(panel.current_panel_bg != "0")
1567 end_pos.y += panel_bg_border * 2;
1569 if(panel_bg_padding)
1571 panel_pos += '1 1 0' * panel_bg_padding;
1572 panel_size -= '2 2 0' * panel_bg_padding;
1578 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1580 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1582 int column = 0, j = 0;
1583 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1584 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1591 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1592 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1593 else if(!((j + column) & 1) && sbt_highlight)
1594 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1596 str = count_ordinal(i+1);
1597 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1598 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1599 str = ColorTranslateRGB(grecordholder[i]);
1601 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1602 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1604 pos.y += 1.25 * hud_fontsize.y;
1606 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1610 pos.x += panel_size.x / columns;
1611 pos.y = panel_pos.y;
1614 strfree(zoned_name_self);
1616 panel_size.x += panel_bg_padding * 2; // restore initial width
1620 float scoreboard_time;
1621 bool have_weapon_stats;
1622 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1624 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1626 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1629 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1630 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1636 if (!have_weapon_stats)
1638 FOREACH(Weapons, it != WEP_Null, {
1639 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1640 if (weapon_stats >= 0)
1642 have_weapon_stats = true;
1646 if (!have_weapon_stats)
1653 bool have_item_stats;
1654 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1656 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1658 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1661 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1662 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1668 if (!have_item_stats)
1670 IL_EACH(default_order_items, true, {
1671 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1673 int q = g_inventory.inv_items[it.m_id];
1674 //q = 1; // debug: display all items
1677 have_item_stats = true;
1682 if (!have_item_stats)
1689 void Scoreboard_Draw()
1691 if(!autocvar__hud_configure)
1693 if(!hud_draw_maximized) return;
1695 // frametime checks allow to toggle the scoreboard even when the game is paused
1696 if(scoreboard_active) {
1697 if (scoreboard_fade_alpha < 1)
1698 scoreboard_time = time;
1699 if(hud_configure_menu_open == 1)
1700 scoreboard_fade_alpha = 1;
1701 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1702 if (scoreboard_fadeinspeed && frametime)
1703 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1705 scoreboard_fade_alpha = 1;
1706 if(hud_fontsize_str != autocvar_hud_fontsize)
1708 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1709 Scoreboard_initFieldSizes();
1710 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1714 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1715 if (scoreboard_fadeoutspeed && frametime)
1716 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1718 scoreboard_fade_alpha = 0;
1721 if (!scoreboard_fade_alpha)
1723 scoreboard_acc_fade_alpha = 0;
1724 scoreboard_itemstats_fade_alpha = 0;
1729 scoreboard_fade_alpha = 0;
1731 if (autocvar_hud_panel_scoreboard_dynamichud)
1734 HUD_Scale_Disable();
1736 if(scoreboard_fade_alpha <= 0)
1738 panel_fade_alpha *= scoreboard_fade_alpha;
1739 HUD_Panel_LoadCvars();
1741 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1742 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1743 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1744 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1745 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1746 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1748 // don't overlap with con_notify
1749 if(!autocvar__hud_configure)
1750 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1752 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1753 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1754 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1755 panel_size.x = fixed_scoreboard_width;
1757 Scoreboard_UpdatePlayerTeams();
1759 vector pos = panel_pos;
1764 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1766 // Begin of Game Info Section
1767 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1768 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1770 // Game Info: Game Type
1771 str = MapInfo_Type_ToText(gametype);
1772 draw_beginBoldFont();
1773 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);
1776 // Game Info: Game Detail
1777 float tl = STAT(TIMELIMIT);
1778 float fl = STAT(FRAGLIMIT);
1779 float ll = STAT(LEADLIMIT);
1780 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1783 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1784 if(!gametype.m_hidelimits)
1789 str = strcat(str, "^7 / "); // delimiter
1792 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1793 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1794 (teamscores_label(ts_primary) == "fastest") ? "" :
1795 TranslateScoresLabel(teamscores_label(ts_primary))));
1799 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1800 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1801 (scores_label(ps_primary) == "fastest") ? "" :
1802 TranslateScoresLabel(scores_label(ps_primary))));
1807 if(tl > 0 || fl > 0)
1810 if (ll_and_fl && fl > 0)
1811 str = strcat(str, "^7 & ");
1813 str = strcat(str, "^7 / ");
1818 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1819 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1820 (teamscores_label(ts_primary) == "fastest") ? "" :
1821 TranslateScoresLabel(teamscores_label(ts_primary))));
1825 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1826 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1827 (scores_label(ps_primary) == "fastest") ? "" :
1828 TranslateScoresLabel(scores_label(ps_primary))));
1833 pos.y += sb_gameinfo_type_fontsize.y;
1834 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
1836 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1837 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1838 // End of Game Info Section
1840 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1841 if(panel.current_panel_bg != "0")
1842 pos.y += panel_bg_border;
1844 // Draw the scoreboard
1845 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1848 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1852 vector panel_bg_color_save = panel_bg_color;
1853 vector team_score_baseoffset;
1854 vector team_size_baseoffset;
1855 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1857 // put team score to the left of scoreboard (and team size to the right)
1858 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1859 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1860 if(panel.current_panel_bg != "0")
1862 team_score_baseoffset.x -= panel_bg_border;
1863 team_size_baseoffset.x += panel_bg_border;
1868 // put team score to the right of scoreboard (and team size to the left)
1869 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1870 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1871 if(panel.current_panel_bg != "0")
1873 team_score_baseoffset.x += panel_bg_border;
1874 team_size_baseoffset.x -= panel_bg_border;
1878 int team_size_total = 0;
1879 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1881 // calculate team size total (sum of all team sizes)
1882 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1883 if(tm.team != NUM_SPECTATOR)
1884 team_size_total += tm.team_size;
1887 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1889 if(tm.team == NUM_SPECTATOR)
1894 draw_beginBoldFont();
1895 vector rgb = Team_ColorRGB(tm.team);
1896 str = ftos(tm.(teamscores(ts_primary)));
1897 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1899 // team score on the left (default)
1900 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1904 // team score on the right
1905 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1907 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1909 // team size (if set to show on the side)
1910 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1912 // calculate the starting position for the whole team size info string
1913 str = sprintf("%d/%d", tm.team_size, team_size_total);
1914 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1916 // team size on the left
1917 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1921 // team size on the right
1922 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1924 str = sprintf("%d", tm.team_size);
1925 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1926 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1927 str = sprintf("/%d", team_size_total);
1928 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1932 // secondary score, e.g. keyhunt
1933 if(ts_primary != ts_secondary)
1935 str = ftos(tm.(teamscores(ts_secondary)));
1936 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1939 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1944 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1947 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1950 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1951 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1952 else if(panel_bg_color_team > 0)
1953 panel_bg_color = rgb * panel_bg_color_team;
1955 panel_bg_color = rgb;
1956 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1958 panel_bg_color = panel_bg_color_save;
1962 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1963 if(tm.team != NUM_SPECTATOR)
1966 // display it anyway
1967 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1970 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1971 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1972 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1973 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1975 if(MUTATOR_CALLHOOK(ShowRankings)) {
1976 string ranktitle = M_ARGV(0, string);
1977 if(race_speedaward) {
1978 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);
1979 pos.y += 1.25 * hud_fontsize.y;
1981 if(race_speedaward_alltimebest) {
1982 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);
1983 pos.y += 1.25 * hud_fontsize.y;
1985 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1988 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1991 for(pl = players.sort_next; pl; pl = pl.sort_next)
1993 if(pl.team == NUM_SPECTATOR)
1995 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1996 if(tm.team == NUM_SPECTATOR)
1998 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1999 draw_beginBoldFont();
2000 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2002 pos.y += 1.25 * hud_fontsize.y;
2004 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2005 pos.y += 1.25 * hud_fontsize.y;
2012 // print information about respawn status
2013 float respawn_time = STAT(RESPAWN_TIME);
2017 if(respawn_time < 0)
2019 // a negative number means we are awaiting respawn, time value is still the same
2020 respawn_time *= -1; // remove mark now that we checked it
2022 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2023 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2025 str = sprintf(_("^1Respawning in ^3%s^1..."),
2026 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2027 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2029 count_seconds(ceil(respawn_time - time))
2033 else if(time < respawn_time)
2035 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2036 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2037 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2039 count_seconds(ceil(respawn_time - time))
2043 else if(time >= respawn_time)
2044 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2046 pos.y += 1.2 * hud_fontsize.y;
2047 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2050 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;