1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/main.qh>
5 #include <client/miscfunctions.qh>
6 #include <client/hud/panel/racetimer.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 void Scoreboard_Draw_Export(int fh)
21 // allow saving cvars that aesthetically change the panel into hud skin files
22 HUD_Write_Cvar("hud_panel_scoreboard_fadeinspeed");
23 HUD_Write_Cvar("hud_panel_scoreboard_fadeoutspeed");
24 HUD_Write_Cvar("hud_panel_scoreboard_respawntime_decimals");
25 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_alpha");
26 HUD_Write_Cvar("hud_panel_scoreboard_table_bg_scale");
27 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha");
28 HUD_Write_Cvar("hud_panel_scoreboard_table_fg_alpha_self");
29 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight");
30 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha");
31 HUD_Write_Cvar("hud_panel_scoreboard_table_highlight_alpha_self");
32 HUD_Write_Cvar("hud_panel_scoreboard_bg_teams_color_team");
33 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_doublerows");
34 HUD_Write_Cvar("hud_panel_scoreboard_accuracy_nocolors");
37 const int MAX_SBT_FIELDS = MAX_SCORE;
39 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
40 float sbt_field_size[MAX_SBT_FIELDS + 1];
41 string sbt_field_title[MAX_SBT_FIELDS + 1];
44 string autocvar_hud_fontsize;
45 string hud_fontsize_str;
48 vector duel_score_fontsize;
49 vector duel_name_fontsize;
50 vector duel_score_size;
54 float sbt_fg_alpha_self;
56 float sbt_highlight_alpha;
57 float sbt_highlight_alpha_self;
59 // provide basic panel cvars to old clients
60 // TODO remove them after a future release (0.8.2+)
61 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
62 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
63 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
64 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
65 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
66 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
67 noref string autocvar_hud_panel_scoreboard_bg_border = "";
68 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
70 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
71 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
72 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
73 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
74 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
75 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
76 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
77 bool autocvar_hud_panel_scoreboard_table_highlight = true;
78 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
79 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
80 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
81 float autocvar_hud_panel_scoreboard_namesize = 15;
82 float autocvar_hud_panel_scoreboard_team_size_position = 0;
84 bool autocvar_hud_panel_scoreboard_accuracy = true;
85 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
86 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
87 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
88 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
90 bool autocvar_hud_panel_scoreboard_dynamichud = false;
92 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
93 bool autocvar_hud_panel_scoreboard_others_showscore = true;
94 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
95 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
96 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
98 // mode 0: returns translated label
99 // mode 1: prints name and description of all the labels
100 string Label_getInfo(string label, int mode)
103 label = "bckills"; // first case in the switch
107 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
108 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
109 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")));
110 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
111 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
112 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
113 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
114 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
115 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
116 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
117 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
118 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
119 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
120 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
121 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
122 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
123 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
124 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
125 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
126 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
127 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
128 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
129 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
130 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
131 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
132 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
133 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
134 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")));
135 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
136 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
137 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
138 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
139 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
140 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
141 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
142 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
143 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
144 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
145 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
146 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
147 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
148 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
149 default: return label;
154 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
155 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
157 void Scoreboard_InitScores()
161 ps_primary = ps_secondary = NULL;
162 ts_primary = ts_secondary = -1;
163 FOREACH(Scores, true, {
164 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
165 if(f == SFL_SORT_PRIO_PRIMARY)
167 if(f == SFL_SORT_PRIO_SECONDARY)
170 if(ps_secondary == NULL)
171 ps_secondary = ps_primary;
173 for(i = 0; i < MAX_TEAMSCORE; ++i)
175 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
176 if(f == SFL_SORT_PRIO_PRIMARY)
178 if(f == SFL_SORT_PRIO_SECONDARY)
181 if(ts_secondary == -1)
182 ts_secondary = ts_primary;
184 Cmd_Scoreboard_SetFields(0);
188 void Scoreboard_UpdatePlayerTeams()
192 for(pl = players.sort_next; pl; pl = pl.sort_next)
195 int Team = entcs_GetScoreTeam(pl.sv_entnum);
196 if(SetTeam(pl, Team))
199 Scoreboard_UpdatePlayerPos(pl);
203 pl = players.sort_next;
208 print(strcat("PNUM: ", ftos(num), "\n"));
213 int Scoreboard_CompareScore(int vl, int vr, int f)
215 TC(int, vl); TC(int, vr); TC(int, f);
216 if(f & SFL_ZERO_IS_WORST)
218 if(vl == 0 && vr != 0)
220 if(vl != 0 && vr == 0)
224 return IS_INCREASING(f);
226 return IS_DECREASING(f);
230 float Scoreboard_ComparePlayerScores(entity left, entity right)
233 vl = entcs_GetTeam(left.sv_entnum);
234 vr = entcs_GetTeam(right.sv_entnum);
246 if(vl == NUM_SPECTATOR)
248 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
250 if(!left.gotscores && right.gotscores)
255 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
259 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
263 FOREACH(Scores, true, {
264 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
265 if (r >= 0) return r;
268 if (left.sv_entnum < right.sv_entnum)
274 void Scoreboard_UpdatePlayerPos(entity player)
277 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
279 SORT_SWAP(player, ent);
281 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
283 SORT_SWAP(ent, player);
287 float Scoreboard_CompareTeamScores(entity left, entity right)
291 if(left.team == NUM_SPECTATOR)
293 if(right.team == NUM_SPECTATOR)
296 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
300 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
304 for(i = 0; i < MAX_TEAMSCORE; ++i)
306 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
311 if (left.team < right.team)
317 void Scoreboard_UpdateTeamPos(entity Team)
320 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
322 SORT_SWAP(Team, ent);
324 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
326 SORT_SWAP(ent, Team);
330 void Cmd_Scoreboard_Help()
332 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
333 LOG_HELP(_("Usage:"));
334 LOG_HELP("^2scoreboard_columns_set ^3default");
335 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
336 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
337 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
338 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
339 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
340 LOG_HELP(_("The following field names are recognized (case insensitive):"));
346 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
347 "of game types, then a slash, to make the field show up only in these\n"
348 "or in all but these game types. You can also specify 'all' as a\n"
349 "field to show all fields available for the current game mode."));
352 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
353 "include/exclude ALL teams/noteams game modes."));
356 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
357 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
358 "right of the vertical bar aligned to the right."));
359 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
360 "other gamemodes except DM."));
363 // NOTE: adding a gametype with ? to not warn for an optional field
364 // make sure it's excluded in a previous exclusive rule, if any
365 // otherwise the previous exclusive rule warns anyway
366 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
367 #define SCOREBOARD_DEFAULT_COLUMNS \
368 "ping pl fps name |" \
369 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
370 " -teams,lms/deaths +ft,tdm/deaths" \
372 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
373 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
374 " +tdm,ft,dom,ons,as/teamkills"\
375 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
376 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
377 " +lms/lives +lms/rank" \
378 " +kh/kckills +kh/losses +kh/caps" \
379 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
380 " +as/objectives +nb/faults +nb/goals" \
381 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
382 " +dom/ticks +dom/takes" \
383 " -lms,rc,cts,inv,nb/score"
385 void Cmd_Scoreboard_SetFields(int argc)
390 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
394 return; // do nothing, we don't know gametype and scores yet
396 // sbt_fields uses strunzone on the titles!
397 if(!sbt_field_title[0])
398 for(i = 0; i < MAX_SBT_FIELDS; ++i)
399 sbt_field_title[i] = strzone("(null)");
401 // TODO: re enable with gametype dependant cvars?
402 if(argc < 3) // no arguments provided
403 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
406 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
410 if(argv(2) == "default" || argv(2) == "expand_default")
412 if(argv(2) == "expand_default")
413 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
414 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
416 else if(argv(2) == "all")
418 string s = "ping pl name |"; // scores without a label
419 FOREACH(Scores, true, {
421 if(it != ps_secondary)
422 if(scores_label(it) != "")
423 s = strcat(s, " ", scores_label(it));
425 if(ps_secondary != ps_primary)
426 s = strcat(s, " ", scores_label(ps_secondary));
427 s = strcat(s, " ", scores_label(ps_primary));
428 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
435 hud_fontsize = HUD_GetFontsize("hud_fontsize");
437 duel_score_fontsize = hud_fontsize * 3;
438 duel_name_fontsize = hud_fontsize * 1.5;
439 duel_score_size = vec2(duel_score_fontsize.x * 1.5, duel_score_fontsize.y * 1.25);
441 for(i = 1; i < argc - 1; ++i)
444 bool nocomplain = false;
445 if(substring(str, 0, 1) == "?")
448 str = substring(str, 1, strlen(str) - 1);
451 slash = strstrofs(str, "/", 0);
454 pattern = substring(str, 0, slash);
455 str = substring(str, slash + 1, strlen(str) - (slash + 1));
457 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
461 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
462 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
463 str = strtolower(str);
468 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
469 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
470 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
471 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
472 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
473 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
474 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
475 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
476 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
477 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
480 FOREACH(Scores, true, {
481 if (str == strtolower(scores_label(it))) {
483 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
493 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
497 sbt_field[sbt_num_fields] = j;
500 if(j == ps_secondary)
501 have_secondary = true;
506 if(sbt_num_fields >= MAX_SBT_FIELDS)
510 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
512 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
513 have_secondary = true;
514 if(ps_primary == ps_secondary)
515 have_secondary = true;
516 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
518 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
522 strunzone(sbt_field_title[sbt_num_fields]);
523 for(i = sbt_num_fields; i > 0; --i)
525 sbt_field_title[i] = sbt_field_title[i-1];
526 sbt_field_size[i] = sbt_field_size[i-1];
527 sbt_field[i] = sbt_field[i-1];
529 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
530 sbt_field[0] = SP_NAME;
532 LOG_INFO("fixed missing field 'name'");
536 strunzone(sbt_field_title[sbt_num_fields]);
537 for(i = sbt_num_fields; i > 1; --i)
539 sbt_field_title[i] = sbt_field_title[i-1];
540 sbt_field_size[i] = sbt_field_size[i-1];
541 sbt_field[i] = sbt_field[i-1];
543 sbt_field_title[1] = strzone("|");
544 sbt_field[1] = SP_SEPARATOR;
545 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
547 LOG_INFO("fixed missing field '|'");
550 else if(!have_separator)
552 strcpy(sbt_field_title[sbt_num_fields], "|");
553 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
554 sbt_field[sbt_num_fields] = SP_SEPARATOR;
556 LOG_INFO("fixed missing field '|'");
560 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
561 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
562 sbt_field[sbt_num_fields] = ps_secondary;
564 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
568 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
569 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
570 sbt_field[sbt_num_fields] = ps_primary;
572 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
576 sbt_field[sbt_num_fields] = SP_END;
580 vector sbt_field_rgb;
581 string sbt_field_icon0;
582 string sbt_field_icon1;
583 string sbt_field_icon2;
584 vector sbt_field_icon0_rgb;
585 vector sbt_field_icon1_rgb;
586 vector sbt_field_icon2_rgb;
587 string Scoreboard_GetName(entity pl)
589 if(ready_waiting && pl.ready)
591 sbt_field_icon0 = "gfx/scoreboard/player_ready";
595 int f = entcs_GetClientColors(pl.sv_entnum);
597 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
598 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
599 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
600 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
601 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
604 return entcs_GetName(pl.sv_entnum);
607 string Scoreboard_GetField(entity pl, PlayerScoreField field)
609 float tmp, num, denom;
612 sbt_field_rgb = '1 1 1';
613 sbt_field_icon0 = "";
614 sbt_field_icon1 = "";
615 sbt_field_icon2 = "";
616 sbt_field_icon0_rgb = '1 1 1';
617 sbt_field_icon1_rgb = '1 1 1';
618 sbt_field_icon2_rgb = '1 1 1';
623 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
624 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
629 tmp = max(0, min(60, f-20)) / 60; // 20-80 range is green
630 sbt_field_rgb = '0 1 0' + '1 0 1' * tmp;
632 tmp = max(0, min(220, f-80)) / 220; // 80-300 range is red
633 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
640 f = pl.ping_packetloss;
641 tmp = pl.ping_movementloss;
642 if(f == 0 && tmp == 0)
644 str = ftos(ceil(f * 100));
646 str = strcat(str, "~", ftos(ceil(tmp * 100)));
647 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
648 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
652 return Scoreboard_GetName(pl);
655 f = pl.(scores(SP_KILLS));
656 f -= pl.(scores(SP_SUICIDES));
660 num = pl.(scores(SP_KILLS));
661 denom = pl.(scores(SP_DEATHS));
664 sbt_field_rgb = '0 1 0';
665 str = sprintf("%d", num);
666 } else if(num <= 0) {
667 sbt_field_rgb = '1 0 0';
668 str = sprintf("%.1f", num/denom);
670 str = sprintf("%.1f", num/denom);
674 f = pl.(scores(SP_KILLS));
675 f -= pl.(scores(SP_DEATHS));
678 sbt_field_rgb = '0 1 0';
680 sbt_field_rgb = '1 1 1';
682 sbt_field_rgb = '1 0 0';
688 float elo = pl.(scores(SP_ELO));
690 case -1: return "...";
691 case -2: return _("N/A");
692 default: return ftos(elo);
698 float fps = pl.(scores(SP_FPS));
701 sbt_field_rgb = '1 1 1';
702 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
704 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
705 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
709 case SP_DMG: case SP_DMGTAKEN:
710 return sprintf("%.1f k", pl.(scores(field)) / 1000);
712 default: case SP_SCORE:
713 tmp = pl.(scores(field));
714 f = scores_flags(field);
715 if(field == ps_primary)
716 sbt_field_rgb = '1 1 0';
717 else if(field == ps_secondary)
718 sbt_field_rgb = '0 1 1';
720 sbt_field_rgb = '1 1 1';
721 return ScoreString(f, tmp);
726 float sbt_fixcolumnwidth_len;
727 float sbt_fixcolumnwidth_iconlen;
728 float sbt_fixcolumnwidth_marginlen;
730 string Scoreboard_FixColumnWidth(int i, string str)
736 sbt_fixcolumnwidth_iconlen = 0;
738 if(sbt_field_icon0 != "")
740 sz = draw_getimagesize(sbt_field_icon0);
742 if(sbt_fixcolumnwidth_iconlen < f)
743 sbt_fixcolumnwidth_iconlen = f;
746 if(sbt_field_icon1 != "")
748 sz = draw_getimagesize(sbt_field_icon1);
750 if(sbt_fixcolumnwidth_iconlen < f)
751 sbt_fixcolumnwidth_iconlen = f;
754 if(sbt_field_icon2 != "")
756 sz = draw_getimagesize(sbt_field_icon2);
758 if(sbt_fixcolumnwidth_iconlen < f)
759 sbt_fixcolumnwidth_iconlen = f;
762 if(sbt_fixcolumnwidth_iconlen != 0)
764 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
765 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
768 sbt_fixcolumnwidth_marginlen = 0;
770 if(sbt_field[i] == SP_NAME) // name gets all remaining space
773 float remaining_space = 0;
774 for(j = 0; j < sbt_num_fields; ++j)
776 if (sbt_field[i] != SP_SEPARATOR)
777 remaining_space += sbt_field_size[j] + hud_fontsize.x;
778 sbt_field_size[i] = panel_size.x - remaining_space;
780 if (sbt_fixcolumnwidth_iconlen != 0)
781 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
782 float namesize = panel_size.x - remaining_space;
783 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
784 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
786 max_namesize = vid_conwidth - remaining_space;
789 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
791 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
792 if(sbt_field_size[i] < f)
793 sbt_field_size[i] = f;
798 void Scoreboard_initFieldSizes()
800 for(int i = 0; i < sbt_num_fields; ++i)
802 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
803 Scoreboard_FixColumnWidth(i, "");
807 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
810 vector column_dim = eY * panel_size.y;
812 column_dim.y -= 1.25 * hud_fontsize.y;
813 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
814 pos.x += hud_fontsize.x * 0.5;
815 for(i = 0; i < sbt_num_fields; ++i)
817 if(sbt_field[i] == SP_SEPARATOR)
819 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
822 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
823 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
824 pos.x += column_dim.x;
826 if(sbt_field[i] == SP_SEPARATOR)
828 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
829 for(i = sbt_num_fields - 1; i > 0; --i)
831 if(sbt_field[i] == SP_SEPARATOR)
834 pos.x -= sbt_field_size[i];
839 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
840 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
843 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
844 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
845 pos.x -= hud_fontsize.x;
850 pos.y += 1.25 * hud_fontsize.y;
854 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
856 TC(bool, is_self); TC(int, pl_number);
858 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
860 vector h_pos = item_pos;
861 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
862 // alternated rows highlighting
864 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
865 else if((sbt_highlight) && (!(pl_number % 2)))
866 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
868 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
870 vector pos = item_pos;
871 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
873 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);
875 pos.x += hud_fontsize.x * 0.5;
876 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
877 vector tmp = '0 0 0';
879 PlayerScoreField field;
880 for(i = 0; i < sbt_num_fields; ++i)
882 field = sbt_field[i];
883 if(field == SP_SEPARATOR)
886 if(is_spec && field != SP_NAME && field != SP_PING) {
887 pos.x += sbt_field_size[i] + hud_fontsize.x;
890 str = Scoreboard_GetField(pl, field);
891 str = Scoreboard_FixColumnWidth(i, str);
893 pos.x += sbt_field_size[i] + hud_fontsize.x;
895 if(field == SP_NAME) {
896 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
897 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
899 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
900 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
903 tmp.x = sbt_field_size[i] + hud_fontsize.x;
904 if(sbt_field_icon0 != "")
905 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 if(sbt_field_icon1 != "")
907 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
908 if(sbt_field_icon2 != "")
909 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
912 if(sbt_field[i] == SP_SEPARATOR)
914 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
915 for(i = sbt_num_fields-1; i > 0; --i)
917 field = sbt_field[i];
918 if(field == SP_SEPARATOR)
921 if(is_spec && field != SP_NAME && field != SP_PING) {
922 pos.x -= sbt_field_size[i] + hud_fontsize.x;
926 str = Scoreboard_GetField(pl, field);
927 str = Scoreboard_FixColumnWidth(i, str);
929 if(field == SP_NAME) {
930 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
931 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
933 tmp.x = sbt_fixcolumnwidth_len;
934 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
937 tmp.x = sbt_field_size[i];
938 if(sbt_field_icon0 != "")
939 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
940 if(sbt_field_icon1 != "")
941 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 if(sbt_field_icon2 != "")
943 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 pos.x -= sbt_field_size[i] + hud_fontsize.x;
949 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
952 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
955 vector h_pos = item_pos;
956 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
958 bool complete = (this_team == NUM_SPECTATOR);
961 if((sbt_highlight) && (!(pl_number % 2)))
962 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
964 vector pos = item_pos;
965 pos.x += hud_fontsize.x * 0.5;
966 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
968 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
970 width_limit -= stringwidth("...", false, hud_fontsize);
971 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
972 static float max_name_width = 0;
975 float min_fieldsize = 0;
976 float fieldpadding = hud_fontsize.x * 0.25;
977 if(this_team == NUM_SPECTATOR)
979 if(autocvar_hud_panel_scoreboard_spectators_showping)
980 min_fieldsize = stringwidth("999", false, hud_fontsize);
982 else if(autocvar_hud_panel_scoreboard_others_showscore)
983 min_fieldsize = stringwidth("99", false, hud_fontsize);
984 for(i = 0; pl; pl = pl.sort_next)
986 if(pl.team != this_team)
992 if(this_team == NUM_SPECTATOR)
994 if(autocvar_hud_panel_scoreboard_spectators_showping)
995 field = Scoreboard_GetField(pl, SP_PING);
997 else if(autocvar_hud_panel_scoreboard_others_showscore)
998 field = Scoreboard_GetField(pl, SP_SCORE);
1000 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
1001 float column_width = stringwidth(str, true, hud_fontsize);
1002 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1004 if(column_width > max_name_width)
1005 max_name_width = column_width;
1006 column_width = max_name_width;
1010 fieldsize = stringwidth(field, false, hud_fontsize);
1011 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1014 if(pos.x + column_width > width_limit)
1019 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1024 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1025 pos.y += hud_fontsize.y * 1.25;
1029 vector name_pos = pos;
1030 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1031 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1032 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1035 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1036 h_size.y = hud_fontsize.y;
1037 vector field_pos = pos;
1038 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1039 field_pos.x += column_width - h_size.x;
1041 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1042 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1043 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1047 h_size.x = column_width + hud_fontsize.x * 0.25;
1048 h_size.y = hud_fontsize.y;
1049 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1051 pos.x += column_width;
1052 pos.x += hud_fontsize.x;
1054 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1057 vector Scoreboard_Duel_DrawPickup(vector pos, bool skinned, string icon, vector sz, float number, bool invert)
1059 vector tmp_in = pos;
1060 vector tmp_sz, tmp_sz2;
1065 picpath = strcat(hud_skin_path, "/", icon);
1066 if(precache_pic(picpath) == "")
1067 picpath = strcat("gfx/hud/default/", icon);
1072 tmp_sz = draw_getimagesize(picpath);
1073 tmp_sz2 = vec2(sz.y*(tmp_sz.x/tmp_sz.y), sz.y);
1075 tmp_in.x = pos.x + ((sz.x - tmp_sz2.x) / 2);
1076 drawpic(tmp_in, picpath, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1080 tmp_in.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1082 tmp_in.x -= hud_fontsize.x * 0.25 + hud_fontsize.x;
1083 tmp_in.y += (tmp_sz2.y - hud_fontsize.y) / 2;
1084 drawstring(tmp_in, ftos(number), hud_fontsize, '0.5 0.5 0.5', panel_fg_alpha, DRAWFLAG_NORMAL);
1086 pos.y += sz.y * 1.1;
1090 void Scoreboard_Duel_DrawTable(vector pos, bool invert, entity pl, entity tm)
1092 vector tmp, tmp_in, tmp_sz, tmp_acc;
1095 float average_acc = 0;
1101 // Stop here if there are no scores available
1102 if(pl.team != tm.team) return;
1105 tmp.x += panel_bg_padding;
1106 tmp.y += panel_bg_padding;
1107 panel_size.x -= panel_bg_padding * 2;
1110 // drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", tmp, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1113 if(invert) { tmp.x += panel_size.x; tmp.x -= duel_score_size.x; }
1114 drawfill(tmp, duel_score_size, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1117 tmp_str = ftos(pl.(scores(SP_SCORE)));
1119 tmp_in.x += (duel_score_size.x / 2) - (stringwidth(tmp_str, true, duel_score_fontsize) / 2);
1120 tmp_in.y += (duel_score_size.y / 2) - (duel_score_fontsize.y / 2);
1122 draw_beginBoldFont();
1123 drawstring(tmp_in, tmp_str, duel_score_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1127 tmp_str = Scoreboard_GetField(pl, SP_NAME);
1130 tmp_in.x -= stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1132 tmp_in.x += duel_score_size.x + duel_name_fontsize.x * 0.5;
1133 tmp_in.y += (duel_score_size.y / 2) - (duel_name_fontsize.y / 2);
1134 drawcolorcodedstring(tmp_in, tmp_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1137 float column_width = panel_size.x / 5;
1138 tmp.x = pos.x + panel_bg_padding;
1139 tmp.y += hud_fontsize.y * 3 + hud_fontsize.y;
1144 i = (invert ? 4 : 0);
1145 column_dim = vec2(column_width * 4, hud_fontsize.y);
1147 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("kills", false, hud_fontsize) / 2),
1148 "kills", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1149 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("dmg", false, hud_fontsize) / 2),
1150 "dmg", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1151 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("acc", false, hud_fontsize) / 2),
1152 "acc", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1153 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("hits", false, hud_fontsize) / 2),
1154 "hits", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1155 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("ping", false, hud_fontsize) / 2),
1156 "ping", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1158 tmp.x = pos.x + panel_bg_padding;
1159 tmp.y += hud_fontsize.y;
1162 i = (invert ? 4 : 0);
1164 tmp_str = ftos(pl.(scores(SP_KILLS)));
1165 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1166 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1168 tmp_str = ftos(pl.(scores(SP_DMG)));
1169 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1170 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1172 tmp_acc = tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2);
1179 tmp_str = Scoreboard_GetField(pl, SP_PING);
1180 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1181 tmp_str, hud_fontsize * 1.25, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1183 tmp.x = pos.x + panel_bg_padding;
1184 tmp.y += hud_fontsize.y * 2;
1188 int total_weapons = 0;
1191 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1192 FOREACH(Weapons, it != WEP_Null, {
1193 WepSet set = it.m_wepset;
1194 if (!(weapons_inmap & set))
1196 if (it.spawnflags & WEP_TYPE_OTHER)
1199 int weapon_cnt_fired = pl.accuracy_cnt_fired[i - WEP_FIRST];
1200 int weapon_cnt_hit = pl.accuracy_cnt_hit[i - WEP_FIRST];
1201 int weapon_acc = floor((weapon_cnt_hit / weapon_cnt_fired) * 100);
1202 average_acc += weapon_acc;
1207 int c = (invert ? 4 : 0);
1209 drawfill(tmp_in + eX * column_width * (invert ? 1 : 0), column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1211 draw_str = ftos(pl.accuracy_frags[i - WEP_FIRST]);
1212 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1213 draw_str, hud_fontsize, '0.5 0.5 0.5', panel_fg_alpha, DRAWFLAG_NORMAL);
1215 draw_str = ftos(pl.accuracy_hit[i - WEP_FIRST]);
1216 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1217 draw_str, hud_fontsize, '0.5 0.5 0.5', panel_fg_alpha, DRAWFLAG_NORMAL);
1219 draw_str = sprintf("%d%%", weapon_acc);
1220 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1221 draw_str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1223 draw_str = strcat(ftos(weapon_cnt_hit), " / ", ftos(weapon_cnt_fired));
1224 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * (column_width / 2) - eX * stringwidth("36 /", false, hud_fontsize),
1225 draw_str,hud_fontsize, '0.5 0.5 0.5', panel_fg_alpha, DRAWFLAG_NORMAL);
1229 tmp_in.x = pos.x + panel_size.x - panel_bg_padding - hud_fontsize.x / 2;
1230 drawpic_aspect_skin(tmp_in, it.model2, vec2(50, hud_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1233 tmp_in.x = pos.x + panel_bg_padding;
1234 tmp_in.y += hud_fontsize.y * 1.25;
1236 if(weapon_cnt_fired)
1239 average_acc = floor((average_acc / total_weapons) + 0.5);
1241 // draw total accuracy now
1242 tmp_str = sprintf("%d%%", average_acc);
1243 drawstring(tmp_acc - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1244 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1247 vector icon_sz = vec2(column_width, hud_fontsize.y*1.5);
1250 tmp.x += column_width * 4;
1252 drawstring(tmp + eX * ((column_width - stringwidth("medals", false, hud_fontsize)) / 2),
1253 "medals", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1254 tmp.y += hud_fontsize.y * 1.25;
1256 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/yoda", icon_sz, 0, invert);
1257 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/airshot", icon_sz, 0, invert);
1258 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/firstblood", icon_sz, 0, invert);
1261 drawstring(tmp + eX * ((column_width - stringwidth("items", false, hud_fontsize)) / 2),
1262 "items", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1263 tmp.y += hud_fontsize.y * 1.25;
1265 FOREACH(Items, it.m_id == ITEM_ArmorMega.m_id || it.m_id == ITEM_HealthMega.m_id, {
1266 tmp = Scoreboard_Duel_DrawPickup(tmp, true, it.m_icon, icon_sz, 0, invert);
1268 if(it.m_id == REGISTRY_MAX(Items))
1272 vector Scoreboard_MakeDuelTable(vector pos, entity tm, vector rgb, vector bg_size)
1274 vector end_pos = pos;
1275 float screen_half = panel_size.x / 2;
1276 float weapon_margin = hud_fontsize.x;
1279 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1280 int disownedcnt = 0;
1282 FOREACH(Weapons, it != WEP_Null, {
1283 WepSet set = it.m_wepset;
1284 if(it.spawnflags & WEP_TYPE_OTHER)
1289 if (!(weapons_inmap & set))
1291 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1298 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1299 panel_size.x = screen_half - weapon_margin;
1300 panel_size.y = (duel_score_size.y * 3) + (hud_fontsize.y * 1.25 * weapon_cnt);
1302 entity pl_left = players.sort_next;
1303 entity pl_right = pl_left.sort_next;
1305 Scoreboard_Duel_DrawTable(pos, true, pl_left, tm);
1306 Scoreboard_Duel_DrawTable(pos + eX * screen_half + eX * weapon_margin, false, pl_right, tm);
1308 end_pos.y += panel_size.y + (panel_bg_padding * 2);
1309 panel_size.x = screen_half * 2;
1313 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1315 int max_players = 999;
1316 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1318 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1321 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1322 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1323 height /= team_count;
1326 height -= panel_bg_padding * 2; // - padding
1327 max_players = floor(height / (hud_fontsize.y * 1.25));
1328 if(max_players <= 1)
1330 if(max_players == tm.team_size)
1335 entity me = playerslots[current_player];
1337 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1338 panel_size.y += panel_bg_padding * 2;
1341 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1342 if(panel.current_panel_bg != "0")
1343 end_pos.y += panel_bg_border * 2;
1345 if(panel_bg_padding)
1347 panel_pos += '1 1 0' * panel_bg_padding;
1348 panel_size -= '2 2 0' * panel_bg_padding;
1352 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1356 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1358 pos.y += 1.25 * hud_fontsize.y;
1361 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1363 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1366 // print header row and highlight columns
1367 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1369 // fill the table and draw the rows
1370 bool is_self = false;
1371 bool self_shown = false;
1373 for(pl = players.sort_next; pl; pl = pl.sort_next)
1375 if(pl.team != tm.team)
1377 if(i == max_players - 2 && pl != me)
1379 if(!self_shown && me.team == tm.team)
1381 Scoreboard_DrawItem(pos, rgb, me, true, i);
1383 pos.y += 1.25 * hud_fontsize.y;
1387 if(i >= max_players - 1)
1389 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1392 is_self = (pl.sv_entnum == current_player);
1393 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1396 pos.y += 1.25 * hud_fontsize.y;
1400 panel_size.x += panel_bg_padding * 2; // restore initial width
1404 bool Scoreboard_WouldDraw()
1406 if (MUTATOR_CALLHOOK(DrawScoreboard))
1408 else if (QuickMenu_IsOpened())
1410 else if (HUD_Radar_Clickable())
1412 else if (scoreboard_showscores)
1414 else if (intermission == 1)
1416 else if (intermission == 2)
1418 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1419 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1423 else if (scoreboard_showscores_force)
1428 float average_accuracy;
1429 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1433 if (scoreboard_fade_alpha == 1)
1434 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1436 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1438 vector initial_pos = pos;
1440 WepSet weapons_stat = WepSet_GetFromStat();
1441 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1442 int disownedcnt = 0;
1444 FOREACH(Weapons, it != WEP_Null, {
1445 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1447 WepSet set = it.m_wepset;
1448 if(it.spawnflags & WEP_TYPE_OTHER)
1453 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1455 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1462 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1463 if (weapon_cnt <= 0) return pos;
1466 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1468 int columnns = ceil(weapon_cnt / rows);
1470 float weapon_height = 29;
1471 float height = hud_fontsize.y + weapon_height;
1473 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);
1474 pos.y += 1.25 * hud_fontsize.y;
1475 if(panel.current_panel_bg != "0")
1476 pos.y += panel_bg_border;
1479 panel_size.y = height * rows;
1480 panel_size.y += panel_bg_padding * 2;
1482 float panel_bg_alpha_save = panel_bg_alpha;
1483 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1485 panel_bg_alpha = panel_bg_alpha_save;
1487 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1488 if(panel.current_panel_bg != "0")
1489 end_pos.y += panel_bg_border * 2;
1491 if(panel_bg_padding)
1493 panel_pos += '1 1 0' * panel_bg_padding;
1494 panel_size -= '2 2 0' * panel_bg_padding;
1498 vector tmp = panel_size;
1500 float weapon_width = tmp.x / columnns / rows;
1503 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1507 // column highlighting
1508 for (int i = 0; i < columnns; ++i)
1510 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);
1513 for (int i = 0; i < rows; ++i)
1514 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1517 average_accuracy = 0;
1518 int weapons_with_stats = 0;
1520 pos.x += weapon_width / 2;
1522 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1525 Accuracy_LoadColors();
1527 float oldposx = pos.x;
1531 FOREACH(Weapons, it != WEP_Null, {
1532 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1534 WepSet set = it.m_wepset;
1535 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1537 if (it.spawnflags & WEP_TYPE_OTHER)
1541 if (weapon_stats >= 0)
1542 weapon_alpha = sbt_fg_alpha;
1544 weapon_alpha = 0.2 * sbt_fg_alpha;
1547 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1549 if (weapon_stats >= 0) {
1550 weapons_with_stats += 1;
1551 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1554 s = sprintf("%d%%", weapon_stats * 100);
1557 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1559 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1560 rgb = Accuracy_GetColor(weapon_stats);
1562 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1564 tmpos.x += weapon_width * rows;
1565 pos.x += weapon_width * rows;
1566 if (rows == 2 && column == columnns - 1) {
1574 if (weapons_with_stats)
1575 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1577 panel_size.x += panel_bg_padding * 2; // restore initial width
1579 if (scoreboard_acc_fade_alpha == 1)
1581 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1584 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1586 pos.x += hud_fontsize.x * 0.25;
1587 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1588 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1589 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1591 pos.y += hud_fontsize.y;
1596 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1597 float stat_secrets_found, stat_secrets_total;
1598 float stat_monsters_killed, stat_monsters_total;
1602 // get monster stats
1603 stat_monsters_killed = STAT(MONSTERS_KILLED);
1604 stat_monsters_total = STAT(MONSTERS_TOTAL);
1606 // get secrets stats
1607 stat_secrets_found = STAT(SECRETS_FOUND);
1608 stat_secrets_total = STAT(SECRETS_TOTAL);
1610 // get number of rows
1611 if(stat_secrets_total)
1613 if(stat_monsters_total)
1616 // if no rows, return
1620 // draw table header
1621 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1622 pos.y += 1.25 * hud_fontsize.y;
1623 if(panel.current_panel_bg != "0")
1624 pos.y += panel_bg_border;
1627 panel_size.y = hud_fontsize.y * rows;
1628 panel_size.y += panel_bg_padding * 2;
1631 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1632 if(panel.current_panel_bg != "0")
1633 end_pos.y += panel_bg_border * 2;
1635 if(panel_bg_padding)
1637 panel_pos += '1 1 0' * panel_bg_padding;
1638 panel_size -= '2 2 0' * panel_bg_padding;
1642 vector tmp = panel_size;
1645 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1648 if(stat_monsters_total)
1650 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1651 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1655 if(stat_secrets_total)
1657 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1658 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1661 panel_size.x += panel_bg_padding * 2; // restore initial width
1666 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1669 RANKINGS_RECEIVED_CNT = 0;
1670 for (i=RANKINGS_CNT-1; i>=0; --i)
1672 ++RANKINGS_RECEIVED_CNT;
1674 if (RANKINGS_RECEIVED_CNT == 0)
1677 vector hl_rgb = rgb + '0.5 0.5 0.5';
1679 pos.y += hud_fontsize.y;
1680 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1681 pos.y += 1.25 * hud_fontsize.y;
1682 if(panel.current_panel_bg != "0")
1683 pos.y += panel_bg_border;
1688 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1690 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1695 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1697 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1701 float ranksize = 3 * hud_fontsize.x;
1702 float timesize = 5 * hud_fontsize.x;
1703 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1704 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1705 columns = min(columns, RANKINGS_RECEIVED_CNT);
1707 // expand name column to fill the entire row
1708 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1709 namesize += available_space;
1710 columnsize.x += available_space;
1712 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1713 panel_size.y += panel_bg_padding * 2;
1717 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1718 if(panel.current_panel_bg != "0")
1719 end_pos.y += panel_bg_border * 2;
1721 if(panel_bg_padding)
1723 panel_pos += '1 1 0' * panel_bg_padding;
1724 panel_size -= '2 2 0' * panel_bg_padding;
1730 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1732 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1734 int column = 0, j = 0;
1735 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1736 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1743 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1744 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1745 else if(!((j + column) & 1) && sbt_highlight)
1746 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1748 str = count_ordinal(i+1);
1749 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1750 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1751 str = ColorTranslateRGB(grecordholder[i]);
1753 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1754 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1756 pos.y += 1.25 * hud_fontsize.y;
1758 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1762 pos.x += panel_size.x / columns;
1763 pos.y = panel_pos.y;
1766 strfree(zoned_name_self);
1768 panel_size.x += panel_bg_padding * 2; // restore initial width
1772 float scoreboard_time;
1773 bool have_weapon_stats;
1774 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1776 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1778 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1781 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1782 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1788 if (!have_weapon_stats)
1790 FOREACH(Weapons, it != WEP_Null, {
1791 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1792 if (weapon_stats >= 0)
1794 have_weapon_stats = true;
1798 if (!have_weapon_stats)
1805 void Scoreboard_Draw()
1807 if(!autocvar__hud_configure)
1809 if(!hud_draw_maximized) return;
1811 // frametime checks allow to toggle the scoreboard even when the game is paused
1812 if(scoreboard_active) {
1813 if (scoreboard_fade_alpha < 1)
1814 scoreboard_time = time;
1815 if(hud_configure_menu_open == 1)
1816 scoreboard_fade_alpha = 1;
1817 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1818 if (scoreboard_fadeinspeed && frametime)
1819 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1821 scoreboard_fade_alpha = 1;
1822 if(hud_fontsize_str != autocvar_hud_fontsize)
1824 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1825 Scoreboard_initFieldSizes();
1826 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1830 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1831 if (scoreboard_fadeoutspeed && frametime)
1832 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1834 scoreboard_fade_alpha = 0;
1837 if (!scoreboard_fade_alpha)
1839 scoreboard_acc_fade_alpha = 0;
1844 scoreboard_fade_alpha = 0;
1846 if (autocvar_hud_panel_scoreboard_dynamichud)
1849 HUD_Scale_Disable();
1851 if(scoreboard_fade_alpha <= 0)
1853 panel_fade_alpha *= scoreboard_fade_alpha;
1854 HUD_Panel_LoadCvars();
1856 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1857 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1858 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1859 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1860 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1861 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1863 // don't overlap with con_notify
1864 if(!autocvar__hud_configure)
1865 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1867 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1868 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1869 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1870 panel_size.x = fixed_scoreboard_width;
1872 Scoreboard_UpdatePlayerTeams();
1874 vector pos = panel_pos;
1879 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1881 // Begin of Game Info Section
1882 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1883 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1886 //drawcolorcodedstring(pos, "bienvenidoainternet.org", sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1887 //drawpic_aspect(pos + '1 0 0' * (panel_size.x - 150), "gfx/bai_logo", vec2(150, sb_gameinfo_type_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1888 //pos.y += sb_gameinfo_type_fontsize.y;
1890 // Game Info: Game Type
1891 str = MapInfo_Type_ToText(gametype);
1893 draw_beginBoldFont();
1894 //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);
1895 drawcolorcodedstring(pos, str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1898 vector tmp_old_sz = draw_getimagesize("gfx/bai_logo");
1899 float tmp_aspect = tmp_old_sz.x/tmp_old_sz.y;
1900 vector tmp_new_sz = vec2(sb_gameinfo_type_fontsize.y * tmp_aspect, sb_gameinfo_type_fontsize.y);
1902 drawpic(pos + '1 0 0' * (panel_size.x - tmp_new_sz.x), "gfx/bai_logo", tmp_new_sz, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1904 pos.y += sb_gameinfo_type_fontsize.y;
1907 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth_colors(hostname_full, sb_gameinfo_detail_fontsize)), hostname_full, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1909 pos.y += sb_gameinfo_detail_fontsize.y;
1911 // Game Info: Game Detail
1912 float tl = STAT(TIMELIMIT);
1913 float fl = STAT(FRAGLIMIT);
1914 float ll = STAT(LEADLIMIT);
1915 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1918 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1919 if(!gametype.m_hidelimits)
1924 str = strcat(str, "^7 / "); // delimiter
1927 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1928 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1929 (teamscores_label(ts_primary) == "fastest") ? "" :
1930 TranslateScoresLabel(teamscores_label(ts_primary))));
1934 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1935 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1936 (scores_label(ps_primary) == "fastest") ? "" :
1937 TranslateScoresLabel(scores_label(ps_primary))));
1942 if(tl > 0 || fl > 0)
1945 if (ll_and_fl && fl > 0)
1946 str = strcat(str, "^7 & ");
1948 str = strcat(str, "^7 / ");
1953 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1954 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1955 (teamscores_label(ts_primary) == "fastest") ? "" :
1956 TranslateScoresLabel(teamscores_label(ts_primary))));
1960 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1961 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1962 (scores_label(ps_primary) == "fastest") ? "" :
1963 TranslateScoresLabel(scores_label(ps_primary))));
1968 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
1970 str = sprintf(_("^7Map: ^2%s"), shortmapname);
1971 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
1972 // End of Game Info Section
1974 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
1975 if(panel.current_panel_bg != "0")
1976 pos.y += panel_bg_border;
1978 // Draw the scoreboard
1979 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1982 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1986 vector panel_bg_color_save = panel_bg_color;
1987 vector team_score_baseoffset;
1988 vector team_size_baseoffset;
1989 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
1991 // put team score to the left of scoreboard (and team size to the right)
1992 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1993 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
1994 if(panel.current_panel_bg != "0")
1996 team_score_baseoffset.x -= panel_bg_border;
1997 team_size_baseoffset.x += panel_bg_border;
2002 // put team score to the right of scoreboard (and team size to the left)
2003 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2004 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2005 if(panel.current_panel_bg != "0")
2007 team_score_baseoffset.x += panel_bg_border;
2008 team_size_baseoffset.x -= panel_bg_border;
2012 int team_size_total = 0;
2013 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2015 // calculate team size total (sum of all team sizes)
2016 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2017 if(tm.team != NUM_SPECTATOR)
2018 team_size_total += tm.team_size;
2021 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2023 if(tm.team == NUM_SPECTATOR)
2028 draw_beginBoldFont();
2029 vector rgb = Team_ColorRGB(tm.team);
2030 str = ftos(tm.(teamscores(ts_primary)));
2031 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2033 // team score on the left (default)
2034 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 3);
2038 // team score on the right
2039 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 3);
2041 drawstring(str_pos, str, hud_fontsize * 3, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2043 // team size (if set to show on the side)
2044 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2046 // calculate the starting position for the whole team size info string
2047 str = sprintf("%d/%d", tm.team_size, team_size_total);
2048 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2050 // team size on the left
2051 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2055 // team size on the right
2056 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2058 str = sprintf("%d", tm.team_size);
2059 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2060 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2061 str = sprintf("/%d", team_size_total);
2062 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2066 // secondary score, e.g. keyhunt
2067 if(ts_primary != ts_secondary)
2069 str = ftos(tm.(teamscores(ts_secondary)));
2070 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2073 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2078 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2081 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2084 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2085 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2086 else if(panel_bg_color_team > 0)
2087 panel_bg_color = rgb * panel_bg_color_team;
2089 panel_bg_color = rgb;
2090 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2092 panel_bg_color = panel_bg_color_save;
2094 else if(gametype == MAPINFO_TYPE_DUEL)
2096 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2097 if(tm.team != NUM_SPECTATOR)
2100 // z411 make DUEL TABLE
2101 pos = Scoreboard_MakeDuelTable(pos, tm, panel_bg_color, bg_size);
2105 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2106 if(tm.team != NUM_SPECTATOR)
2109 // display it anyway
2110 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2113 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2114 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2116 if(MUTATOR_CALLHOOK(ShowRankings)) {
2117 string ranktitle = M_ARGV(0, string);
2118 if(race_speedaward) {
2119 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);
2120 pos.y += 1.25 * hud_fontsize.y;
2122 if(race_speedaward_alltimebest) {
2123 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);
2124 pos.y += 1.25 * hud_fontsize.y;
2126 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2129 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2132 for(pl = players.sort_next; pl; pl = pl.sort_next)
2134 if(pl.team == NUM_SPECTATOR)
2136 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2137 if(tm.team == NUM_SPECTATOR)
2139 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2140 draw_beginBoldFont();
2141 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2143 pos.y += 1.25 * hud_fontsize.y;
2145 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2146 pos.y += 1.25 * hud_fontsize.y;
2153 // print information about respawn status
2154 float respawn_time = STAT(RESPAWN_TIME);
2158 if(respawn_time < 0)
2160 // a negative number means we are awaiting respawn, time value is still the same
2161 respawn_time *= -1; // remove mark now that we checked it
2163 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2164 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2166 str = sprintf(_("^1Respawning in ^3%s^1..."),
2167 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2168 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2170 count_seconds(ceil(respawn_time - time))
2174 else if(time < respawn_time)
2176 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2177 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2178 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2180 count_seconds(ceil(respawn_time - time))
2184 else if(time >= respawn_time)
2185 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2187 pos.y += 1.2 * hud_fontsize.y;
2188 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2191 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;