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);
1171 WepSet weapons_stat = WepSet_GetFromStat();
1172 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1173 int disownedcnt = 0;
1175 FOREACH(Weapons, it != WEP_Null, {
1176 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1178 WepSet set = it.m_wepset;
1179 if(it.spawnflags & WEP_TYPE_OTHER)
1184 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1186 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1193 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1194 if (weapon_cnt <= 0) return pos;
1197 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1199 int columnns = ceil(weapon_cnt / rows);
1201 float weapon_height = 29;
1202 float height = hud_fontsize.y + weapon_height;
1204 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);
1205 pos.y += 1.25 * hud_fontsize.y;
1206 if(panel.current_panel_bg != "0")
1207 pos.y += panel_bg_border;
1210 panel_size.y = height * rows;
1211 panel_size.y += panel_bg_padding * 2;
1213 float panel_bg_alpha_save = panel_bg_alpha;
1214 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1216 panel_bg_alpha = panel_bg_alpha_save;
1218 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1219 if(panel.current_panel_bg != "0")
1220 end_pos.y += panel_bg_border * 2;
1222 if(panel_bg_padding)
1224 panel_pos += '1 1 0' * panel_bg_padding;
1225 panel_size -= '2 2 0' * panel_bg_padding;
1229 vector tmp = panel_size;
1231 float weapon_width = tmp.x / columnns / rows;
1234 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1238 // column highlighting
1239 for (int i = 0; i < columnns; ++i)
1241 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);
1244 for (int i = 0; i < rows; ++i)
1245 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1248 average_accuracy = 0;
1249 int weapons_with_stats = 0;
1251 pos.x += weapon_width / 2;
1253 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1256 Accuracy_LoadColors();
1258 float oldposx = pos.x;
1262 FOREACH(Weapons, it != WEP_Null, {
1263 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1265 WepSet set = it.m_wepset;
1266 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1268 if (it.spawnflags & WEP_TYPE_OTHER)
1272 if (weapon_stats >= 0)
1273 weapon_alpha = sbt_fg_alpha;
1275 weapon_alpha = 0.2 * sbt_fg_alpha;
1278 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1280 if (weapon_stats >= 0) {
1281 weapons_with_stats += 1;
1282 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1285 s = sprintf("%d%%", weapon_stats * 100);
1288 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1290 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1291 rgb = Accuracy_GetColor(weapon_stats);
1293 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1295 tmpos.x += weapon_width * rows;
1296 pos.x += weapon_width * rows;
1297 if (rows == 2 && column == columnns - 1) {
1305 if (weapons_with_stats)
1306 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1308 panel_size.x += panel_bg_padding * 2; // restore initial width
1313 .bool uninteresting;
1314 STATIC_INIT(default_order_items_label)
1316 IL_EACH(default_order_items, true, {
1317 if(!(it.instanceOfPowerup
1318 || it == ITEM_HealthMega || it == ITEM_HealthBig
1319 || it == ITEM_ArmorMega || it == ITEM_ArmorBig
1322 it.uninteresting = true;
1327 vector Scoreboard_ItemStats_Draw(vector pos, vector rgb, vector bg_size)
1329 scoreboard_itemstats_fade_alpha = min(scoreboard_fade_alpha, scoreboard_itemstats_fade_alpha + frametime * 10);
1331 int disowned_cnt = 0;
1332 int uninteresting_cnt = 0;
1333 IL_EACH(default_order_items, true, {
1334 int q = g_inventory.inv_items[it.m_id];
1335 //q = 1; // debug: display all items
1336 if (autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting)
1337 ++uninteresting_cnt;
1341 int items_cnt = REGISTRY_COUNT(Items) - uninteresting_cnt;
1342 int n = items_cnt - disowned_cnt;
1343 if (n <= 0) return pos;
1345 int rows = (autocvar_hud_panel_scoreboard_itemstats_doublerows && n >= floor(REGISTRY_COUNT(Items) / 2)) ? 2 : 1;
1346 int columnns = max(6, ceil(n / rows));
1349 float fontsize = height * 1/3;
1350 float item_height = height * 2/3;
1352 drawstring(pos + eX * panel_bg_padding, _("Item stats"), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1353 pos.y += 1.25 * hud_fontsize.y;
1354 if(panel.current_panel_bg != "0")
1355 pos.y += panel_bg_border;
1358 panel_size.y = height * rows;
1359 panel_size.y += panel_bg_padding * 2;
1361 float panel_bg_alpha_save = panel_bg_alpha;
1362 panel_bg_alpha *= scoreboard_itemstats_fade_alpha;
1364 panel_bg_alpha = panel_bg_alpha_save;
1366 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1367 if(panel.current_panel_bg != "0")
1368 end_pos.y += panel_bg_border * 2;
1370 if(panel_bg_padding)
1372 panel_pos += '1 1 0' * panel_bg_padding;
1373 panel_size -= '2 2 0' * panel_bg_padding;
1377 vector tmp = panel_size;
1379 float item_width = tmp.x / columnns / rows;
1382 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_itemstats_fade_alpha, DRAWFLAG_NORMAL);
1386 // column highlighting
1387 for (int i = 0; i < columnns; ++i)
1389 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);
1392 for (int i = 0; i < rows; ++i)
1393 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);
1397 pos.x += item_width / 2;
1399 float oldposx = pos.x;
1403 IL_EACH(default_order_items, !(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting), {
1404 int n = g_inventory.inv_items[it.m_id];
1405 //n = 1 + floor(i * 3 + 4.8) % 7; // debug: display a value for each item
1406 if (n <= 0) continue;
1407 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);
1409 float padding = (item_width - stringwidth(s, false, '1 0 0' * fontsize)) / 2; // center
1410 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);
1411 tmpos.x += item_width * rows;
1412 pos.x += item_width * rows;
1413 if (rows == 2 && column == columnns - 1) {
1421 panel_size.x += panel_bg_padding * 2; // restore initial width
1426 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1428 pos.x += hud_fontsize.x * 0.25;
1429 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1430 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1431 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1433 pos.y += hud_fontsize.y;
1438 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1439 float stat_secrets_found, stat_secrets_total;
1440 float stat_monsters_killed, stat_monsters_total;
1444 // get monster stats
1445 stat_monsters_killed = STAT(MONSTERS_KILLED);
1446 stat_monsters_total = STAT(MONSTERS_TOTAL);
1448 // get secrets stats
1449 stat_secrets_found = STAT(SECRETS_FOUND);
1450 stat_secrets_total = STAT(SECRETS_TOTAL);
1452 // get number of rows
1453 if(stat_secrets_total)
1455 if(stat_monsters_total)
1458 // if no rows, return
1462 // draw table header
1463 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1464 pos.y += 1.25 * hud_fontsize.y;
1465 if(panel.current_panel_bg != "0")
1466 pos.y += panel_bg_border;
1469 panel_size.y = hud_fontsize.y * rows;
1470 panel_size.y += panel_bg_padding * 2;
1473 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1474 if(panel.current_panel_bg != "0")
1475 end_pos.y += panel_bg_border * 2;
1477 if(panel_bg_padding)
1479 panel_pos += '1 1 0' * panel_bg_padding;
1480 panel_size -= '2 2 0' * panel_bg_padding;
1484 vector tmp = panel_size;
1487 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1490 if(stat_monsters_total)
1492 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1493 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1497 if(stat_secrets_total)
1499 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1500 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1503 panel_size.x += panel_bg_padding * 2; // restore initial width
1508 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1511 RANKINGS_RECEIVED_CNT = 0;
1512 for (i=RANKINGS_CNT-1; i>=0; --i)
1514 ++RANKINGS_RECEIVED_CNT;
1516 if (RANKINGS_RECEIVED_CNT == 0)
1519 vector hl_rgb = rgb + '0.5 0.5 0.5';
1521 pos.y += hud_fontsize.y;
1522 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1523 pos.y += 1.25 * hud_fontsize.y;
1524 if(panel.current_panel_bg != "0")
1525 pos.y += panel_bg_border;
1530 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1532 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1537 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1539 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1543 float ranksize = 3 * hud_fontsize.x;
1544 float timesize = 5 * hud_fontsize.x;
1545 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1546 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1547 columns = min(columns, RANKINGS_RECEIVED_CNT);
1549 // expand name column to fill the entire row
1550 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1551 namesize += available_space;
1552 columnsize.x += available_space;
1554 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1555 panel_size.y += panel_bg_padding * 2;
1559 vector end_pos = panel_pos + eY * (panel_size.y + 0.5 * hud_fontsize.y);
1560 if(panel.current_panel_bg != "0")
1561 end_pos.y += panel_bg_border * 2;
1563 if(panel_bg_padding)
1565 panel_pos += '1 1 0' * panel_bg_padding;
1566 panel_size -= '2 2 0' * panel_bg_padding;
1572 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1574 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1576 int column = 0, j = 0;
1577 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1578 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1585 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1586 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1587 else if(!((j + column) & 1) && sbt_highlight)
1588 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1590 str = count_ordinal(i+1);
1591 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1592 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1593 str = ColorTranslateRGB(grecordholder[i]);
1595 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1596 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1598 pos.y += 1.25 * hud_fontsize.y;
1600 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1604 pos.x += panel_size.x / columns;
1605 pos.y = panel_pos.y;
1608 strfree(zoned_name_self);
1610 panel_size.x += panel_bg_padding * 2; // restore initial width
1614 float scoreboard_time;
1615 bool have_weapon_stats;
1616 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1618 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1620 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1623 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1624 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1630 if (!have_weapon_stats)
1632 FOREACH(Weapons, it != WEP_Null, {
1633 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1634 if (weapon_stats >= 0)
1636 have_weapon_stats = true;
1640 if (!have_weapon_stats)
1647 bool have_item_stats;
1648 bool Scoreboard_ItemStats_WouldDraw(float ypos)
1650 if (MUTATOR_CALLHOOK(DrawScoreboardItemStats))
1652 if (!autocvar_hud_panel_scoreboard_itemstats || !g_inventory || warmup_stage || ypos > 0.91 * vid_conheight)
1655 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_itemstats_showdelay
1656 && ypos > autocvar_hud_panel_scoreboard_itemstats_showdelay_minpos * vid_conheight
1662 if (!have_item_stats)
1664 IL_EACH(default_order_items, true, {
1665 if (!(autocvar_hud_panel_scoreboard_itemstats_filter && it.uninteresting))
1667 int q = g_inventory.inv_items[it.m_id];
1668 //q = 1; // debug: display all items
1671 have_item_stats = true;
1676 if (!have_item_stats)
1683 void Scoreboard_Draw()
1685 if(!autocvar__hud_configure)
1687 if(!hud_draw_maximized) return;
1689 // frametime checks allow to toggle the scoreboard even when the game is paused
1690 if(scoreboard_active) {
1691 if (scoreboard_fade_alpha < 1)
1692 scoreboard_time = time;
1693 if(hud_configure_menu_open == 1)
1694 scoreboard_fade_alpha = 1;
1695 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1696 if (scoreboard_fadeinspeed && frametime)
1697 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1699 scoreboard_fade_alpha = 1;
1700 if(hud_fontsize_str != autocvar_hud_fontsize)
1702 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1703 Scoreboard_initFieldSizes();
1704 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1708 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1709 if (scoreboard_fadeoutspeed && frametime)
1710 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1712 scoreboard_fade_alpha = 0;
1715 if (!scoreboard_fade_alpha)
1717 scoreboard_acc_fade_alpha = 0;
1718 scoreboard_itemstats_fade_alpha = 0;
1723 scoreboard_fade_alpha = 0;
1725 if (autocvar_hud_panel_scoreboard_dynamichud)
1728 HUD_Scale_Disable();
1730 if(scoreboard_fade_alpha <= 0)
1732 panel_fade_alpha *= scoreboard_fade_alpha;
1733 HUD_Panel_LoadCvars();
1735 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1736 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1737 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1738 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1739 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1740 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1742 // don't overlap with con_notify
1743 if(!autocvar__hud_configure)
1744 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1746 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1747 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1748 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1749 panel_size.x = fixed_scoreboard_width;
1751 Scoreboard_UpdatePlayerTeams();
1753 float initial_pos_y = panel_pos.y;
1754 vector pos = panel_pos;
1759 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1761 // Begin of Game Info Section
1762 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1763 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1765 // Game Info: Game Type
1766 str = MapInfo_Type_ToText(gametype);
1767 draw_beginBoldFont();
1768 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);
1771 // Game Info: Game Detail
1772 float tl = STAT(TIMELIMIT);
1773 float fl = STAT(FRAGLIMIT);
1774 float ll = STAT(LEADLIMIT);
1775 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1778 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1779 if(!gametype.m_hidelimits)
1784 str = strcat(str, "^7 / "); // delimiter
1787 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1788 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1789 (teamscores_label(ts_primary) == "fastest") ? "" :
1790 TranslateScoresLabel(teamscores_label(ts_primary))));
1794 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1795 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1796 (scores_label(ps_primary) == "fastest") ? "" :
1797 TranslateScoresLabel(scores_label(ps_primary))));
1802 if(tl > 0 || fl > 0)
1805 if (ll_and_fl && fl > 0)
1806 str = strcat(str, "^7 & ");
1808 str = strcat(str, "^7 / ");
1813 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1814 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1815 (teamscores_label(ts_primary) == "fastest") ? "" :
1816 TranslateScoresLabel(teamscores_label(ts_primary))));
1820 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1821 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1822 (scores_label(ps_primary) == "fastest") ? "" :
1823 TranslateScoresLabel(scores_label(ps_primary))));
1828 pos.y += sb_gameinfo_type_fontsize.y;
1829 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
1831 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1832 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1833 // End of Game Info Section
1835 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1836 if(panel.current_panel_bg != "0")
1837 pos.y += panel_bg_border;
1839 // Draw the scoreboard
1840 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1843 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1847 vector panel_bg_color_save = panel_bg_color;
1848 vector team_score_baseoffset;
1849 vector team_size_baseoffset;
1850 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1852 // put team score to the left of scoreboard (and team size to the right)
1853 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1854 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1855 if(panel.current_panel_bg != "0")
1857 team_score_baseoffset.x -= panel_bg_border;
1858 team_size_baseoffset.x += panel_bg_border;
1863 // put team score to the right of scoreboard (and team size to the left)
1864 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1865 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1866 if(panel.current_panel_bg != "0")
1868 team_score_baseoffset.x += panel_bg_border;
1869 team_size_baseoffset.x -= panel_bg_border;
1873 int team_size_total = 0;
1874 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1876 // calculate team size total (sum of all team sizes)
1877 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1878 if(tm.team != NUM_SPECTATOR)
1879 team_size_total += tm.team_size;
1882 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1884 if(tm.team == NUM_SPECTATOR)
1889 draw_beginBoldFont();
1890 vector rgb = Team_ColorRGB(tm.team);
1891 str = ftos(tm.(teamscores(ts_primary)));
1892 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1894 // team score on the left (default)
1895 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1899 // team score on the right
1900 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1902 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1904 // team size (if set to show on the side)
1905 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
1907 // calculate the starting position for the whole team size info string
1908 str = sprintf("%d/%d", tm.team_size, team_size_total);
1909 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
1911 // team size on the left
1912 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1916 // team size on the right
1917 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
1919 str = sprintf("%d", tm.team_size);
1920 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1921 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
1922 str = sprintf("/%d", team_size_total);
1923 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1927 // secondary score, e.g. keyhunt
1928 if(ts_primary != ts_secondary)
1930 str = ftos(tm.(teamscores(ts_secondary)));
1931 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1934 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
1939 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
1942 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1945 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1946 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1947 else if(panel_bg_color_team > 0)
1948 panel_bg_color = rgb * panel_bg_color_team;
1950 panel_bg_color = rgb;
1951 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1953 panel_bg_color = panel_bg_color_save;
1957 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1958 if(tm.team != NUM_SPECTATOR)
1961 // display it anyway
1962 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1965 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1966 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1967 if (Scoreboard_ItemStats_WouldDraw(pos.y))
1968 pos = Scoreboard_ItemStats_Draw(pos, panel_bg_color, bg_size);
1970 if(MUTATOR_CALLHOOK(ShowRankings)) {
1971 string ranktitle = M_ARGV(0, string);
1972 if(race_speedaward) {
1973 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);
1974 pos.y += 1.25 * hud_fontsize.y;
1976 if(race_speedaward_alltimebest) {
1977 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);
1978 pos.y += 1.25 * hud_fontsize.y;
1980 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
1983 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1986 for(pl = players.sort_next; pl; pl = pl.sort_next)
1988 if(pl.team == NUM_SPECTATOR)
1990 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1991 if(tm.team == NUM_SPECTATOR)
1993 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1994 draw_beginBoldFont();
1995 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1997 pos.y += 1.25 * hud_fontsize.y;
1999 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2000 pos.y += 1.25 * hud_fontsize.y;
2007 // print information about respawn status
2008 float respawn_time = STAT(RESPAWN_TIME);
2012 if(respawn_time < 0)
2014 // a negative number means we are awaiting respawn, time value is still the same
2015 respawn_time *= -1; // remove mark now that we checked it
2017 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2018 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2020 str = sprintf(_("^1Respawning in ^3%s^1..."),
2021 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2022 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2024 count_seconds(ceil(respawn_time - time))
2028 else if(time < respawn_time)
2030 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2031 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2032 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2034 count_seconds(ceil(respawn_time - time))
2038 else if(time >= respawn_time)
2039 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2041 pos.y += 1.2 * hud_fontsize.y;
2042 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2045 pos.y += 2 * hud_fontsize.y;
2046 if (scoreboard_fade_alpha < 1)
2047 scoreboard_bottom = initial_pos_y + (pos.y - initial_pos_y) * scoreboard_fade_alpha;
2048 else if (pos.y != scoreboard_bottom)
2050 if (pos.y > scoreboard_bottom)
2051 scoreboard_bottom = min(pos.y, scoreboard_bottom + frametime * 10 * (pos.y - initial_pos_y));
2053 scoreboard_bottom = max(pos.y, scoreboard_bottom - frametime * 10 * (pos.y - initial_pos_y));