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>
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;
49 vector duel_score_fontsize;
50 vector duel_name_fontsize;
51 vector duel_score_size;
52 vector team_score_fontsize;
53 vector team_name_fontsize;
54 vector team_score_size;
59 float sbt_fg_alpha_self;
61 float sbt_highlight_alpha;
62 float sbt_highlight_alpha_self;
64 // provide basic panel cvars to old clients
65 // TODO remove them after a future release (0.8.2+)
66 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
67 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
68 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
69 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
70 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
71 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
72 noref string autocvar_hud_panel_scoreboard_bg_border = "";
73 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
75 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
76 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
77 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
78 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
79 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
80 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
81 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
82 bool autocvar_hud_panel_scoreboard_table_highlight = true;
83 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
84 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
85 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
86 float autocvar_hud_panel_scoreboard_namesize = 15;
87 float autocvar_hud_panel_scoreboard_team_size_position = 0;
89 bool autocvar_hud_panel_scoreboard_accuracy = true;
90 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
91 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
92 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
93 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
95 bool autocvar_hud_panel_scoreboard_dynamichud = false;
97 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
98 bool autocvar_hud_panel_scoreboard_others_showscore = true;
99 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
100 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
101 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
103 // mode 0: returns translated label
104 // mode 1: prints name and description of all the labels
105 string Label_getInfo(string label, int mode)
108 label = "bckills"; // first case in the switch
112 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
113 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
114 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")));
115 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
116 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
117 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
118 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
119 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
120 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
121 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
122 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
123 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
124 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
125 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
126 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
127 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
128 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
129 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
130 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
131 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
132 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
133 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
134 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
135 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
136 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
137 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
138 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
139 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")));
140 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
141 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
142 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
143 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
144 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
145 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
146 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
147 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
148 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
149 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
150 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
151 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
152 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
153 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
154 default: return label;
159 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
160 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
162 void Scoreboard_InitScores()
166 ps_primary = ps_secondary = NULL;
167 ts_primary = ts_secondary = -1;
168 FOREACH(Scores, true, {
169 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
170 if(f == SFL_SORT_PRIO_PRIMARY)
172 if(f == SFL_SORT_PRIO_SECONDARY)
175 if(ps_secondary == NULL)
176 ps_secondary = ps_primary;
178 for(i = 0; i < MAX_TEAMSCORE; ++i)
180 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
181 if(f == SFL_SORT_PRIO_PRIMARY)
183 if(f == SFL_SORT_PRIO_SECONDARY)
186 if(ts_secondary == -1)
187 ts_secondary = ts_primary;
189 Cmd_Scoreboard_SetFields(0);
193 void Scoreboard_UpdatePlayerTeams()
197 for(pl = players.sort_next; pl; pl = pl.sort_next)
200 int Team = entcs_GetScoreTeam(pl.sv_entnum);
201 if(SetTeam(pl, Team))
204 Scoreboard_UpdatePlayerPos(pl);
208 pl = players.sort_next;
213 print(strcat("PNUM: ", ftos(num), "\n"));
218 int Scoreboard_CompareScore(int vl, int vr, int f)
220 TC(int, vl); TC(int, vr); TC(int, f);
221 if(f & SFL_ZERO_IS_WORST)
223 if(vl == 0 && vr != 0)
225 if(vl != 0 && vr == 0)
229 return IS_INCREASING(f);
231 return IS_DECREASING(f);
235 float Scoreboard_ComparePlayerScores(entity left, entity right)
238 vl = entcs_GetTeam(left.sv_entnum);
239 vr = entcs_GetTeam(right.sv_entnum);
251 if(vl == NUM_SPECTATOR)
253 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
255 if(!left.gotscores && right.gotscores)
260 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
264 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
268 FOREACH(Scores, true, {
269 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
270 if (r >= 0) return r;
273 if (left.sv_entnum < right.sv_entnum)
279 void Scoreboard_UpdatePlayerPos(entity player)
282 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
284 SORT_SWAP(player, ent);
286 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
288 SORT_SWAP(ent, player);
292 float Scoreboard_CompareTeamScores(entity left, entity right)
296 if(left.team == NUM_SPECTATOR)
298 if(right.team == NUM_SPECTATOR)
301 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
305 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
309 for(i = 0; i < MAX_TEAMSCORE; ++i)
311 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
316 if (left.team < right.team)
322 void Scoreboard_UpdateTeamPos(entity Team)
325 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
327 SORT_SWAP(Team, ent);
329 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
331 SORT_SWAP(ent, Team);
335 void Cmd_Scoreboard_Help()
337 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
338 LOG_HELP(_("Usage:"));
339 LOG_HELP("^2scoreboard_columns_set ^3default");
340 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
341 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
342 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
343 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
344 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
345 LOG_HELP(_("The following field names are recognized (case insensitive):"));
351 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
352 "of game types, then a slash, to make the field show up only in these\n"
353 "or in all but these game types. You can also specify 'all' as a\n"
354 "field to show all fields available for the current game mode."));
357 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
358 "include/exclude ALL teams/noteams game modes."));
361 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
362 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
363 "right of the vertical bar aligned to the right."));
364 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
365 "other gamemodes except DM."));
368 // NOTE: adding a gametype with ? to not warn for an optional field
369 // make sure it's excluded in a previous exclusive rule, if any
370 // otherwise the previous exclusive rule warns anyway
371 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
372 #define SCOREBOARD_DEFAULT_COLUMNS \
373 "ping pl fps name |" \
374 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
375 " -teams,lms/deaths +ft,tdm/deaths" \
377 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
378 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
379 " +tdm,ft,dom,ons,as/teamkills"\
380 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
381 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
382 " +lms/lives +lms/rank" \
383 " +kh/kckills +kh/losses +kh/caps" \
384 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
385 " +as/objectives +nb/faults +nb/goals" \
386 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
387 " +dom/ticks +dom/takes" \
388 " -lms,rc,cts,inv,nb/score"
390 void Cmd_Scoreboard_SetFields(int argc)
395 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
399 return; // do nothing, we don't know gametype and scores yet
401 // sbt_fields uses strunzone on the titles!
402 if(!sbt_field_title[0])
403 for(i = 0; i < MAX_SBT_FIELDS; ++i)
404 sbt_field_title[i] = strzone("(null)");
406 // TODO: re enable with gametype dependant cvars?
407 if(argc < 3) // no arguments provided
408 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
411 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
415 if(argv(2) == "default" || argv(2) == "expand_default")
417 if(argv(2) == "expand_default")
418 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
419 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
421 else if(argv(2) == "all")
423 string s = "ping pl name |"; // scores without a label
424 FOREACH(Scores, true, {
426 if(it != ps_secondary)
427 if(scores_label(it) != "")
428 s = strcat(s, " ", scores_label(it));
430 if(ps_secondary != ps_primary)
431 s = strcat(s, " ", scores_label(ps_secondary));
432 s = strcat(s, " ", scores_label(ps_primary));
433 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
440 hud_fontsize = HUD_GetFontsize("hud_fontsize");
442 duel_score_fontsize = hud_fontsize * 3;
443 duel_name_fontsize = hud_fontsize * 1.5;
444 duel_score_size = vec2(duel_score_fontsize.x * 1.5, duel_score_fontsize.y * 1.25);
446 team_score_fontsize = hud_fontsize * 2;
447 team_name_fontsize = hud_fontsize * 1.5;
448 team_score_size = vec2(team_score_fontsize.x * 1.5, team_score_fontsize.y * 1.25);
450 for(i = 1; i < argc - 1; ++i)
453 bool nocomplain = false;
454 if(substring(str, 0, 1) == "?")
457 str = substring(str, 1, strlen(str) - 1);
460 slash = strstrofs(str, "/", 0);
463 pattern = substring(str, 0, slash);
464 str = substring(str, slash + 1, strlen(str) - (slash + 1));
466 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
470 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
471 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
472 str = strtolower(str);
477 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
478 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
479 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
480 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
481 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
482 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
483 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
484 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
485 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
486 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
489 FOREACH(Scores, true, {
490 if (str == strtolower(scores_label(it))) {
492 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
502 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
506 sbt_field[sbt_num_fields] = j;
509 if(j == ps_secondary)
510 have_secondary = true;
515 if(sbt_num_fields >= MAX_SBT_FIELDS)
519 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
521 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
522 have_secondary = true;
523 if(ps_primary == ps_secondary)
524 have_secondary = true;
525 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
527 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
531 strunzone(sbt_field_title[sbt_num_fields]);
532 for(i = sbt_num_fields; i > 0; --i)
534 sbt_field_title[i] = sbt_field_title[i-1];
535 sbt_field_size[i] = sbt_field_size[i-1];
536 sbt_field[i] = sbt_field[i-1];
538 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
539 sbt_field[0] = SP_NAME;
541 LOG_INFO("fixed missing field 'name'");
545 strunzone(sbt_field_title[sbt_num_fields]);
546 for(i = sbt_num_fields; i > 1; --i)
548 sbt_field_title[i] = sbt_field_title[i-1];
549 sbt_field_size[i] = sbt_field_size[i-1];
550 sbt_field[i] = sbt_field[i-1];
552 sbt_field_title[1] = strzone("|");
553 sbt_field[1] = SP_SEPARATOR;
554 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
556 LOG_INFO("fixed missing field '|'");
559 else if(!have_separator)
561 strcpy(sbt_field_title[sbt_num_fields], "|");
562 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
563 sbt_field[sbt_num_fields] = SP_SEPARATOR;
565 LOG_INFO("fixed missing field '|'");
569 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
570 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
571 sbt_field[sbt_num_fields] = ps_secondary;
573 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
577 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
578 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
579 sbt_field[sbt_num_fields] = ps_primary;
581 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
585 sbt_field[sbt_num_fields] = SP_END;
589 vector sbt_field_rgb;
590 string sbt_field_icon0;
591 string sbt_field_icon1;
592 string sbt_field_icon2;
593 vector sbt_field_icon0_rgb;
594 vector sbt_field_icon1_rgb;
595 vector sbt_field_icon2_rgb;
596 string Scoreboard_GetName(entity pl)
598 if(ready_waiting && pl.ready)
600 sbt_field_icon0 = "gfx/scoreboard/player_ready";
604 int f = entcs_GetClientColors(pl.sv_entnum);
606 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
607 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
608 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
609 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
610 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
615 int ccode = entcs_GetCountryCode(pl.sv_entnum);
617 sbt_field_icon0 = strcat("gfx/flags/", ftos(ccode));
620 return entcs_GetName(pl.sv_entnum);
623 string Scoreboard_GetField(entity pl, PlayerScoreField field)
625 float tmp, num, denom;
628 sbt_field_rgb = '1 1 1';
629 sbt_field_icon0 = "";
630 sbt_field_icon1 = "";
631 sbt_field_icon2 = "";
632 sbt_field_icon0_rgb = '1 1 1';
633 sbt_field_icon1_rgb = '1 1 1';
634 sbt_field_icon2_rgb = '1 1 1';
639 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
640 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
645 tmp = max(0, min(60, f-20)) / 60; // 20-80 range is green
646 sbt_field_rgb = '0 1 0' + '1 0 1' * tmp;
648 tmp = max(0, min(220, f-80)) / 220; // 80-300 range is red
649 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
656 f = pl.ping_packetloss;
657 tmp = pl.ping_movementloss;
658 if(f == 0 && tmp == 0)
660 str = ftos(ceil(f * 100));
662 str = strcat(str, "~", ftos(ceil(tmp * 100)));
663 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
664 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
668 return Scoreboard_GetName(pl);
671 f = pl.(scores(SP_KILLS));
672 f -= pl.(scores(SP_SUICIDES));
676 num = pl.(scores(SP_KILLS));
677 denom = pl.(scores(SP_DEATHS));
680 sbt_field_rgb = '0 1 0';
681 str = sprintf("%d", num);
682 } else if(num <= 0) {
683 sbt_field_rgb = '1 0 0';
684 str = sprintf("%.1f", num/denom);
686 str = sprintf("%.1f", num/denom);
690 f = pl.(scores(SP_KILLS));
691 f -= pl.(scores(SP_DEATHS));
694 sbt_field_rgb = '0 1 0';
696 sbt_field_rgb = '1 1 1';
698 sbt_field_rgb = '1 0 0';
704 float elo = pl.(scores(SP_ELO));
706 case -1: return "...";
707 case -2: return _("N/A");
708 default: return ftos(elo);
714 float fps = pl.(scores(SP_FPS));
717 sbt_field_rgb = '1 1 1';
718 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
720 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
721 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
725 case SP_DMG: case SP_DMGTAKEN:
726 return sprintf("%.1f k", pl.(scores(field)) / 1000);
728 default: case SP_SCORE:
729 tmp = pl.(scores(field));
730 f = scores_flags(field);
731 if(field == ps_primary)
732 sbt_field_rgb = '1 1 0';
733 else if(field == ps_secondary)
734 sbt_field_rgb = '0 1 1';
736 sbt_field_rgb = '1 1 1';
737 return ScoreString(f, tmp);
742 float sbt_fixcolumnwidth_len;
743 float sbt_fixcolumnwidth_iconlen;
744 float sbt_fixcolumnwidth_marginlen;
746 string Scoreboard_FixColumnWidth(int i, string str)
752 sbt_fixcolumnwidth_iconlen = 0;
754 if(sbt_field_icon0 != "")
756 sz = draw_getimagesize(sbt_field_icon0);
758 if(sbt_fixcolumnwidth_iconlen < f)
759 sbt_fixcolumnwidth_iconlen = f;
762 if(sbt_field_icon1 != "")
764 sz = draw_getimagesize(sbt_field_icon1);
766 if(sbt_fixcolumnwidth_iconlen < f)
767 sbt_fixcolumnwidth_iconlen = f;
770 if(sbt_field_icon2 != "")
772 sz = draw_getimagesize(sbt_field_icon2);
774 if(sbt_fixcolumnwidth_iconlen < f)
775 sbt_fixcolumnwidth_iconlen = f;
778 if(sbt_fixcolumnwidth_iconlen != 0)
780 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
781 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
784 sbt_fixcolumnwidth_marginlen = 0;
786 if(sbt_field[i] == SP_NAME) // name gets all remaining space
789 float remaining_space = 0;
790 for(j = 0; j < sbt_num_fields; ++j)
792 if (sbt_field[i] != SP_SEPARATOR)
793 remaining_space += sbt_field_size[j] + hud_fontsize.x;
794 sbt_field_size[i] = panel_size.x - remaining_space;
796 if (sbt_fixcolumnwidth_iconlen != 0)
797 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
798 float namesize = panel_size.x - remaining_space;
799 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
800 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
802 max_namesize = vid_conwidth - remaining_space;
805 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
807 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
808 if(sbt_field_size[i] < f)
809 sbt_field_size[i] = f;
814 void Scoreboard_initFieldSizes()
816 for(int i = 0; i < sbt_num_fields; ++i)
818 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
819 Scoreboard_FixColumnWidth(i, "");
823 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
826 vector column_dim = eY * panel_size.y;
828 column_dim.y -= 1.25 * hud_fontsize.y;
829 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
830 pos.x += hud_fontsize.x * 0.5;
831 for(i = 0; i < sbt_num_fields; ++i)
833 if(sbt_field[i] == SP_SEPARATOR)
835 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
838 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
839 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
840 pos.x += column_dim.x;
842 if(sbt_field[i] == SP_SEPARATOR)
844 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
845 for(i = sbt_num_fields - 1; i > 0; --i)
847 if(sbt_field[i] == SP_SEPARATOR)
850 pos.x -= sbt_field_size[i];
855 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
856 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
859 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
860 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
861 pos.x -= hud_fontsize.x;
866 pos.y += 1.25 * hud_fontsize.y;
870 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
872 TC(bool, is_self); TC(int, pl_number);
874 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
876 vector h_pos = item_pos;
877 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
878 // alternated rows highlighting
880 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
881 else if((sbt_highlight) && (!(pl_number % 2)))
882 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
884 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
886 vector pos = item_pos;
887 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
889 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);
891 pos.x += hud_fontsize.x * 0.5;
892 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
893 vector tmp = '0 0 0';
895 PlayerScoreField field;
896 for(i = 0; i < sbt_num_fields; ++i)
898 field = sbt_field[i];
899 if(field == SP_SEPARATOR)
902 if(is_spec && field != SP_NAME && field != SP_PING) {
903 pos.x += sbt_field_size[i] + hud_fontsize.x;
906 str = Scoreboard_GetField(pl, field);
907 str = Scoreboard_FixColumnWidth(i, str);
909 pos.x += sbt_field_size[i] + hud_fontsize.x;
911 if(field == SP_NAME) {
912 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
913 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
915 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
916 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
919 tmp.x = sbt_field_size[i] + hud_fontsize.x;
920 if(sbt_field_icon0 != "")
921 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
922 if(sbt_field_icon1 != "")
923 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
924 if(sbt_field_icon2 != "")
925 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 if(sbt_field[i] == SP_SEPARATOR)
930 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
931 for(i = sbt_num_fields-1; i > 0; --i)
933 field = sbt_field[i];
934 if(field == SP_SEPARATOR)
937 if(is_spec && field != SP_NAME && field != SP_PING) {
938 pos.x -= sbt_field_size[i] + hud_fontsize.x;
942 str = Scoreboard_GetField(pl, field);
943 str = Scoreboard_FixColumnWidth(i, str);
945 if(field == SP_NAME) {
946 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
947 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
949 tmp.x = sbt_fixcolumnwidth_len;
950 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
953 tmp.x = sbt_field_size[i];
954 if(sbt_field_icon0 != "")
955 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
956 if(sbt_field_icon1 != "")
957 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
958 if(sbt_field_icon2 != "")
959 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
960 pos.x -= sbt_field_size[i] + hud_fontsize.x;
965 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
968 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
971 vector h_pos = item_pos;
972 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
974 bool complete = (this_team == NUM_SPECTATOR);
977 if((sbt_highlight) && (!(pl_number % 2)))
978 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
980 vector pos = item_pos;
981 pos.x += hud_fontsize.x * 0.5;
982 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
984 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
986 width_limit -= stringwidth("...", false, hud_fontsize);
987 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
988 static float max_name_width = 0;
991 float min_fieldsize = 0;
992 float fieldpadding = hud_fontsize.x * 0.25;
993 if(this_team == NUM_SPECTATOR)
995 if(autocvar_hud_panel_scoreboard_spectators_showping)
996 min_fieldsize = stringwidth("999", false, hud_fontsize);
998 else if(autocvar_hud_panel_scoreboard_others_showscore)
999 min_fieldsize = stringwidth("99", false, hud_fontsize);
1000 for(i = 0; pl; pl = pl.sort_next)
1002 if(pl.team != this_team)
1004 if(pl == ignored_pl)
1008 if(this_team == NUM_SPECTATOR)
1010 if(autocvar_hud_panel_scoreboard_spectators_showping)
1011 field = Scoreboard_GetField(pl, SP_PING);
1013 else if(autocvar_hud_panel_scoreboard_others_showscore)
1014 field = Scoreboard_GetField(pl, SP_SCORE);
1016 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
1017 float column_width = stringwidth(str, true, hud_fontsize);
1018 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1020 if(column_width > max_name_width)
1021 max_name_width = column_width;
1022 column_width = max_name_width;
1026 fieldsize = stringwidth(field, false, hud_fontsize);
1027 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1030 if(pos.x + column_width > width_limit)
1035 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1040 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1041 pos.y += hud_fontsize.y * 1.25;
1045 vector name_pos = pos;
1046 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1047 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1048 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1051 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1052 h_size.y = hud_fontsize.y;
1053 vector field_pos = pos;
1054 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1055 field_pos.x += column_width - h_size.x;
1057 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1058 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1059 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1063 h_size.x = column_width + hud_fontsize.x * 0.25;
1064 h_size.y = hud_fontsize.y;
1065 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1067 pos.x += column_width;
1068 pos.x += hud_fontsize.x;
1070 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1073 vector Scoreboard_DrawMedal(vector pos, string icon, float height, float number)
1075 if(!number) return pos;
1076 total_medals += number;
1078 vector tmp_sz, tmp_sz2;
1079 tmp_sz = draw_getimagesize(icon);
1080 tmp_sz2 = vec2(height*(tmp_sz.x/tmp_sz.y), height);
1081 string val = ftos(number);
1083 drawpic(pos, icon, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1085 pos.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1086 drawstring(pos + eY * ((tmp_sz2.y - hud_fontsize.y) / 2), val, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1088 pos.x += stringwidth(val, false, hud_fontsize) + hud_fontsize.x * 0.5;
1092 vector Scoreboard_Duel_DrawPickup(vector pos, bool skinned, string icon, vector sz, float number, bool invert)
1094 vector tmp_in = pos;
1095 vector tmp_sz, tmp_sz2;
1100 picpath = strcat(hud_skin_path, "/", icon);
1101 if(precache_pic(picpath) == "")
1102 picpath = strcat("gfx/hud/default/", icon);
1107 tmp_sz = draw_getimagesize(picpath);
1108 tmp_sz2 = vec2(sz.y*(tmp_sz.x/tmp_sz.y), sz.y);
1110 tmp_in.x = pos.x + ((sz.x - tmp_sz2.x) / 2);
1111 drawpic(tmp_in, picpath, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1115 tmp_in.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1117 tmp_in.x -= hud_fontsize.x * 0.25 + hud_fontsize.x;
1118 tmp_in.y += (tmp_sz2.y - hud_fontsize.y) / 2;
1119 drawstring(tmp_in, ftos(number), hud_fontsize, (number ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1121 pos.y += sz.y * 1.1;
1125 void Scoreboard_Duel_DrawTable(vector pos, bool invert, entity pl, entity tm)
1127 vector tmp, tmp_in, tmp_sz, tmp_acc;
1130 float average_acc = 0;
1136 // Stop here if there are no scores available
1137 if(pl.team != tm.team) return;
1140 tmp.x += panel_bg_padding;
1141 tmp.y += panel_bg_padding;
1142 panel_size.x -= panel_bg_padding * 2;
1145 // drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", tmp, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1148 if(invert) { tmp.x += panel_size.x; tmp.x -= duel_score_size.x; }
1149 drawfill(tmp, duel_score_size, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1152 tmp_str = ftos(pl.(scores(SP_SCORE)));
1154 tmp_in.x += (duel_score_size.x / 2) - (stringwidth(tmp_str, true, duel_score_fontsize) / 2);
1155 tmp_in.y += (duel_score_size.y / 2) - (duel_score_fontsize.y / 2);
1157 draw_beginBoldFont();
1158 drawstring(tmp_in, tmp_str, duel_score_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1162 tmp_str = Scoreboard_GetField(pl, SP_NAME);
1165 tmp_in.x -= stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1167 tmp_in.x += duel_score_size.x + duel_name_fontsize.x * 0.5;
1168 tmp_in.y += (duel_score_size.y - duel_name_fontsize.y) / 2;
1169 drawcolorcodedstring(tmp_in, tmp_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1172 if(sbt_field_icon0 != "") {
1173 vector rsz = draw_getimagesize(sbt_field_icon0);
1174 sbt_fixcolumnwidth_iconlen = rsz.x / rsz.y;
1176 tmp_in.x -= hud_fontsize.x * sbt_fixcolumnwidth_iconlen + duel_name_fontsize.x * 0.5;
1178 tmp_in.x += stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1179 tmp_in.y += (duel_name_fontsize.y - hud_fontsize.y) / 2;
1180 drawpic(tmp_in, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1184 float column_width = panel_size.x / 5;
1185 tmp.x = pos.x + panel_bg_padding;
1186 tmp.y += hud_fontsize.y * 3 + hud_fontsize.y;
1191 i = (invert ? 4 : 0);
1192 column_dim = vec2(column_width * 4, hud_fontsize.y);
1194 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("kills", false, hud_fontsize) / 2),
1195 "kills", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1196 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("dmg", false, hud_fontsize) / 2),
1197 "dmg", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1198 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("acc", false, hud_fontsize) / 2),
1199 "acc", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1200 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("hits", false, hud_fontsize) / 2),
1201 "hits", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1202 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("ping", false, hud_fontsize) / 2),
1203 "ping", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1205 tmp.x = pos.x + panel_bg_padding;
1206 tmp.y += hud_fontsize.y;
1209 i = (invert ? 4 : 0);
1211 tmp_str = ftos(pl.(scores(SP_KILLS)));
1212 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1213 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1215 tmp_str = ftos(pl.(scores(SP_DMG)));
1216 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1217 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1219 tmp_acc = tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2);
1226 tmp_str = Scoreboard_GetField(pl, SP_PING);
1227 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1228 tmp_str, hud_fontsize * 1.25, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1230 tmp.x = pos.x + panel_bg_padding;
1231 tmp.y += hud_fontsize.y * 2;
1235 int total_weapons = 0;
1238 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1239 FOREACH(Weapons, it != WEP_Null, {
1240 WepSet set = it.m_wepset;
1241 if (!(weapons_inmap & set))
1243 if (it.spawnflags & WEP_TYPE_OTHER)
1246 int weapon_cnt_fired = pl.accuracy_cnt_fired[i - WEP_FIRST];
1247 int weapon_cnt_hit = pl.accuracy_cnt_hit[i - WEP_FIRST];
1249 if(weapon_cnt_fired)
1250 weapon_acc = floor((weapon_cnt_hit / weapon_cnt_fired) * 100);
1251 average_acc += weapon_acc;
1256 int c = (invert ? 4 : 0);
1258 drawfill(tmp_in + eX * column_width * (invert ? 1 : 0), column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1260 draw_str = ftos(pl.accuracy_frags[i - WEP_FIRST]);
1261 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1262 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1264 draw_str = ftos(pl.accuracy_hit[i - WEP_FIRST]);
1265 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1266 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1268 draw_str = sprintf("%d%%", weapon_acc);
1269 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1270 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1272 draw_str = strcat(ftos(weapon_cnt_hit), " / ", ftos(weapon_cnt_fired));
1273 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * (column_width / 2) - eX * stringwidth("36 /", false, hud_fontsize),
1274 draw_str,hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1278 tmp_in.x = pos.x + panel_size.x - panel_bg_padding - hud_fontsize.x / 2;
1279 drawpic_aspect_skin(tmp_in, it.model2, vec2(50, hud_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1282 tmp_in.x = pos.x + panel_bg_padding;
1283 tmp_in.y += hud_fontsize.y * 1.25;
1285 if(weapon_cnt_fired)
1289 average_acc = floor((average_acc / total_weapons) + 0.5);
1291 // draw total accuracy now
1292 tmp_str = sprintf("%d%%", average_acc);
1293 drawstring(tmp_acc - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1294 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1297 vector icon_sz = vec2(column_width, hud_fontsize.y*1.5);
1300 tmp.x += column_width * 4;
1302 drawstring(tmp + eX * ((column_width - stringwidth("medals", false, hud_fontsize)) / 2),
1303 "medals", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1304 tmp.y += hud_fontsize.y * 1.25;
1306 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/humiliation", icon_sz, pl.(scores(SP_MEDAL_HUMILIATION)), invert);
1307 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/impressive", icon_sz, pl.(scores(SP_MEDAL_IMPRESSIVE)), invert);
1308 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/excellent", icon_sz, pl.(scores(SP_MEDAL_EXCELLENT)), invert);
1311 drawstring(tmp + eX * ((column_width - stringwidth("items", false, hud_fontsize)) / 2),
1312 "items", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1313 tmp.y += hud_fontsize.y * 1.25;
1316 it.m_id == ITEM_ArmorMega.m_id ||
1317 it.m_id == ITEM_HealthMega.m_id ||
1318 it.m_id == ITEM_ArmorBig.m_id, {
1319 tmp = Scoreboard_Duel_DrawPickup(tmp, true, it.m_icon, icon_sz, inventoryslots[pl.sv_entnum].inv_items[it.m_id], invert);
1321 if(it.m_id == REGISTRY_MAX(Items))
1325 vector Scoreboard_MakeDuelTable(vector pos, entity tm, vector rgb, vector bg_size)
1327 vector end_pos = pos;
1328 float screen_half = panel_size.x / 2;
1329 float weapon_margin = hud_fontsize.x;
1331 panel_size.x = screen_half - weapon_margin;
1332 panel_size.y = (duel_score_size.y * 5.5);
1334 entity pl_left = players.sort_next;
1335 entity pl_right = pl_left.sort_next;
1337 Scoreboard_Duel_DrawTable(pos, true, pl_left, tm);
1338 Scoreboard_Duel_DrawTable(pos + eX * screen_half + eX * weapon_margin, false, pl_right, tm);
1340 end_pos.y += panel_size.y + (panel_bg_padding * 2);
1341 panel_size.x = screen_half * 2;
1345 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1347 int max_players = 999;
1348 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1350 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1353 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1354 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1355 height /= team_count;
1358 height -= panel_bg_padding * 2; // - padding
1359 max_players = floor(height / (hud_fontsize.y * 1.25));
1360 if(max_players <= 1)
1362 if(max_players == tm.team_size)
1367 entity me = playerslots[current_player];
1369 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1370 panel_size.y += panel_bg_padding * 2;
1373 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1374 if(panel.current_panel_bg != "0")
1375 end_pos.y += panel_bg_border * 2;
1377 if(panel_bg_padding)
1379 panel_pos += '1 1 0' * panel_bg_padding;
1380 panel_size -= '2 2 0' * panel_bg_padding;
1384 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1388 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1390 pos.y += 1.25 * hud_fontsize.y;
1393 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1395 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1398 // print header row and highlight columns
1399 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1401 // fill the table and draw the rows
1402 bool is_self = false;
1403 bool self_shown = false;
1405 for(pl = players.sort_next; pl; pl = pl.sort_next)
1407 if(pl.team != tm.team)
1409 if(i == max_players - 2 && pl != me)
1411 if(!self_shown && me.team == tm.team)
1413 Scoreboard_DrawItem(pos, rgb, me, true, i);
1415 pos.y += 1.25 * hud_fontsize.y;
1419 if(i >= max_players - 1)
1421 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1424 is_self = (pl.sv_entnum == current_player);
1425 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1428 pos.y += 1.25 * hud_fontsize.y;
1432 panel_size.x += panel_bg_padding * 2; // restore initial width
1436 bool Scoreboard_WouldDraw()
1438 if (MUTATOR_CALLHOOK(DrawScoreboard))
1440 else if (QuickMenu_IsOpened())
1442 else if (HUD_Radar_Clickable())
1444 else if (scoreboard_showscores)
1446 else if (intermission == 1)
1448 else if (intermission == 2)
1450 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1451 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1455 else if (scoreboard_showscores_force)
1460 vector Scoreboard_MedalStats_Draw(vector pos)
1463 float height = hud_fontsize.y * 2;
1465 entity pl = playerslots[current_player];
1467 vector title_pos = pos;
1468 pos.x += 0.5 * hud_fontsize.x + panel_bg_padding;
1469 pos.y += 1.25 * hud_fontsize.y;
1473 pos = Scoreboard_DrawMedal(pos, "gfx/medal/airshot", height, pl.(scores(SP_MEDAL_AIRSHOT)));
1474 pos = Scoreboard_DrawMedal(pos, "gfx/medal/damage", height, pl.(scores(SP_MEDAL_DAMAGE)));
1475 pos = Scoreboard_DrawMedal(pos, "gfx/medal/electrobitch", height, pl.(scores(SP_MEDAL_ELECTROBITCH)));
1476 pos = Scoreboard_DrawMedal(pos, "gfx/medal/excellent", height, pl.(scores(SP_MEDAL_EXCELLENT)));
1477 pos = Scoreboard_DrawMedal(pos, "gfx/medal/firstblood", height, pl.(scores(SP_MEDAL_FIRSTBLOOD)));
1478 pos = Scoreboard_DrawMedal(pos, "gfx/medal/headshot", height, pl.(scores(SP_MEDAL_HEADSHOT)));
1479 pos = Scoreboard_DrawMedal(pos, "gfx/medal/humiliation", height, pl.(scores(SP_MEDAL_HUMILIATION)));
1480 pos = Scoreboard_DrawMedal(pos, "gfx/medal/impressive", height, pl.(scores(SP_MEDAL_IMPRESSIVE)));
1481 pos = Scoreboard_DrawMedal(pos, "gfx/medal/yoda", height, pl.(scores(SP_MEDAL_YODA)));
1483 pos.x += hud_fontsize.x;
1485 pos = Scoreboard_DrawMedal(pos, "gfx/medal/assist", height, pl.(scores(SP_MEDAL_ASSIST)));
1486 pos = Scoreboard_DrawMedal(pos, "gfx/medal/defense", height, pl.(scores(SP_MEDAL_DEFENSE)));
1488 if(!total_medals) return orig;
1490 drawstring(title_pos, sprintf(_("Medal stats (total %d)"), total_medals),
1491 hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1494 pos.y += height + hud_fontsize.y * 0.5;
1498 float average_accuracy;
1499 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1503 if (scoreboard_fade_alpha == 1)
1504 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1506 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1508 vector initial_pos = pos;
1510 WepSet weapons_stat = WepSet_GetFromStat();
1511 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1512 int disownedcnt = 0;
1514 FOREACH(Weapons, it != WEP_Null, {
1515 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1517 WepSet set = it.m_wepset;
1518 if(it.spawnflags & WEP_TYPE_OTHER)
1523 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1525 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1532 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1533 if (weapon_cnt <= 0) return pos;
1536 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1538 int columnns = ceil(weapon_cnt / rows);
1540 float weapon_height = 29;
1541 float height = hud_fontsize.y + weapon_height;
1543 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);
1544 pos.y += 1.25 * hud_fontsize.y;
1545 if(panel.current_panel_bg != "0")
1546 pos.y += panel_bg_border;
1549 panel_size.y = height * rows;
1550 panel_size.y += panel_bg_padding * 2;
1552 float panel_bg_alpha_save = panel_bg_alpha;
1553 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1555 panel_bg_alpha = panel_bg_alpha_save;
1557 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1558 if(panel.current_panel_bg != "0")
1559 end_pos.y += panel_bg_border * 2;
1561 if(panel_bg_padding)
1563 panel_pos += '1 1 0' * panel_bg_padding;
1564 panel_size -= '2 2 0' * panel_bg_padding;
1568 vector tmp = panel_size;
1570 float weapon_width = tmp.x / columnns / rows;
1573 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1577 // column highlighting
1578 for (int i = 0; i < columnns; ++i)
1580 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);
1583 for (int i = 0; i < rows; ++i)
1584 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1587 average_accuracy = 0;
1588 int weapons_with_stats = 0;
1590 pos.x += weapon_width / 2;
1592 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1595 Accuracy_LoadColors();
1597 float oldposx = pos.x;
1601 FOREACH(Weapons, it != WEP_Null, {
1602 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1604 WepSet set = it.m_wepset;
1605 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1607 if (it.spawnflags & WEP_TYPE_OTHER)
1611 if (weapon_stats >= 0)
1612 weapon_alpha = sbt_fg_alpha;
1614 weapon_alpha = 0.2 * sbt_fg_alpha;
1617 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1619 if (weapon_stats >= 0) {
1620 weapons_with_stats += 1;
1621 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1624 s = sprintf("%d%%", weapon_stats * 100);
1627 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1629 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1630 rgb = Accuracy_GetColor(weapon_stats);
1632 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1634 tmpos.x += weapon_width * rows;
1635 pos.x += weapon_width * rows;
1636 if (rows == 2 && column == columnns - 1) {
1644 if (weapons_with_stats)
1645 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1647 panel_size.x += panel_bg_padding * 2; // restore initial width
1649 if (scoreboard_acc_fade_alpha == 1)
1651 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1654 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1656 pos.x += hud_fontsize.x * 0.25;
1657 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1658 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1659 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1661 pos.y += hud_fontsize.y;
1666 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1667 float stat_secrets_found, stat_secrets_total;
1668 float stat_monsters_killed, stat_monsters_total;
1672 // get monster stats
1673 stat_monsters_killed = STAT(MONSTERS_KILLED);
1674 stat_monsters_total = STAT(MONSTERS_TOTAL);
1676 // get secrets stats
1677 stat_secrets_found = STAT(SECRETS_FOUND);
1678 stat_secrets_total = STAT(SECRETS_TOTAL);
1680 // get number of rows
1681 if(stat_secrets_total)
1683 if(stat_monsters_total)
1686 // if no rows, return
1690 // draw table header
1691 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1692 pos.y += 1.25 * hud_fontsize.y;
1693 if(panel.current_panel_bg != "0")
1694 pos.y += panel_bg_border;
1697 panel_size.y = hud_fontsize.y * rows;
1698 panel_size.y += panel_bg_padding * 2;
1701 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1702 if(panel.current_panel_bg != "0")
1703 end_pos.y += panel_bg_border * 2;
1705 if(panel_bg_padding)
1707 panel_pos += '1 1 0' * panel_bg_padding;
1708 panel_size -= '2 2 0' * panel_bg_padding;
1712 vector tmp = panel_size;
1715 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1718 if(stat_monsters_total)
1720 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1721 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1725 if(stat_secrets_total)
1727 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1728 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1731 panel_size.x += panel_bg_padding * 2; // restore initial width
1736 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1739 RANKINGS_RECEIVED_CNT = 0;
1740 for (i=RANKINGS_CNT-1; i>=0; --i)
1742 ++RANKINGS_RECEIVED_CNT;
1744 if (RANKINGS_RECEIVED_CNT == 0)
1747 vector hl_rgb = rgb + '0.5 0.5 0.5';
1749 pos.y += hud_fontsize.y;
1750 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1751 pos.y += 1.25 * hud_fontsize.y;
1752 if(panel.current_panel_bg != "0")
1753 pos.y += panel_bg_border;
1758 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1760 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1765 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1767 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1771 float ranksize = 3 * hud_fontsize.x;
1772 float timesize = 5 * hud_fontsize.x;
1773 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1774 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1775 columns = min(columns, RANKINGS_RECEIVED_CNT);
1777 // expand name column to fill the entire row
1778 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1779 namesize += available_space;
1780 columnsize.x += available_space;
1782 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1783 panel_size.y += panel_bg_padding * 2;
1787 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1788 if(panel.current_panel_bg != "0")
1789 end_pos.y += panel_bg_border * 2;
1791 if(panel_bg_padding)
1793 panel_pos += '1 1 0' * panel_bg_padding;
1794 panel_size -= '2 2 0' * panel_bg_padding;
1800 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1802 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1804 int column = 0, j = 0;
1805 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1806 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1813 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1814 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1815 else if(!((j + column) & 1) && sbt_highlight)
1816 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1818 str = count_ordinal(i+1);
1819 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1820 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1821 str = ColorTranslateRGB(grecordholder[i]);
1823 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1824 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1826 pos.y += 1.25 * hud_fontsize.y;
1828 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1832 pos.x += panel_size.x / columns;
1833 pos.y = panel_pos.y;
1836 strfree(zoned_name_self);
1838 panel_size.x += panel_bg_padding * 2; // restore initial width
1842 float scoreboard_time;
1843 bool have_weapon_stats;
1844 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1846 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1848 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1851 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1852 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1858 if (!have_weapon_stats)
1860 FOREACH(Weapons, it != WEP_Null, {
1861 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1862 if (weapon_stats >= 0)
1864 have_weapon_stats = true;
1868 if (!have_weapon_stats)
1875 void Scoreboard_Draw()
1877 if(!autocvar__hud_configure)
1879 if(!hud_draw_maximized) return;
1881 // frametime checks allow to toggle the scoreboard even when the game is paused
1882 if(scoreboard_active) {
1883 if (scoreboard_fade_alpha < 1)
1884 scoreboard_time = time;
1885 if(hud_configure_menu_open == 1)
1886 scoreboard_fade_alpha = 1;
1887 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1888 if (scoreboard_fadeinspeed && frametime)
1889 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1891 scoreboard_fade_alpha = 1;
1892 if(hud_fontsize_str != autocvar_hud_fontsize)
1894 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1895 Scoreboard_initFieldSizes();
1896 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1900 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1901 if (scoreboard_fadeoutspeed && frametime)
1902 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1904 scoreboard_fade_alpha = 0;
1907 if (!scoreboard_fade_alpha)
1909 scoreboard_acc_fade_alpha = 0;
1914 scoreboard_fade_alpha = 0;
1916 if (autocvar_hud_panel_scoreboard_dynamichud)
1919 HUD_Scale_Disable();
1921 if(scoreboard_fade_alpha <= 0)
1923 panel_fade_alpha *= scoreboard_fade_alpha;
1924 HUD_Panel_LoadCvars();
1926 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1927 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1928 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1929 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1930 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1931 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1933 // don't overlap with con_notify
1934 if(!autocvar__hud_configure)
1935 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1937 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1938 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1939 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1940 panel_size.x = fixed_scoreboard_width;
1942 Scoreboard_UpdatePlayerTeams();
1944 vector pos = panel_pos;
1949 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1951 // Begin of Game Info Section
1952 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1953 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1956 //drawcolorcodedstring(pos, "bienvenidoainternet.org", sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1957 //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);
1958 //pos.y += sb_gameinfo_type_fontsize.y;
1960 // Game Info: Game Type
1961 str = MapInfo_Type_ToText(gametype);
1963 draw_beginBoldFont();
1964 //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);
1965 drawcolorcodedstring(pos, str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1968 vector tmp_old_sz = draw_getimagesize("gfx/bai_logo");
1969 float tmp_aspect = tmp_old_sz.x/tmp_old_sz.y;
1970 vector tmp_new_sz = vec2(sb_gameinfo_type_fontsize.y * tmp_aspect, sb_gameinfo_type_fontsize.y);
1972 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);
1974 pos.y += sb_gameinfo_type_fontsize.y;
1977 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);
1979 pos.y += sb_gameinfo_detail_fontsize.y;
1981 // Game Info: Game Detail
1982 float tl = STAT(TIMELIMIT);
1983 float fl = STAT(FRAGLIMIT);
1984 float ll = STAT(LEADLIMIT);
1985 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1988 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1989 if(!gametype.m_hidelimits)
1994 str = strcat(str, "^7 / "); // delimiter
1997 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1998 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1999 (teamscores_label(ts_primary) == "fastest") ? "" :
2000 TranslateScoresLabel(teamscores_label(ts_primary))));
2004 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
2005 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2006 (scores_label(ps_primary) == "fastest") ? "" :
2007 TranslateScoresLabel(scores_label(ps_primary))));
2012 if(tl > 0 || fl > 0)
2015 if (ll_and_fl && fl > 0)
2016 str = strcat(str, "^7 & ");
2018 str = strcat(str, "^7 / ");
2023 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
2024 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
2025 (teamscores_label(ts_primary) == "fastest") ? "" :
2026 TranslateScoresLabel(teamscores_label(ts_primary))));
2030 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
2031 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2032 (scores_label(ps_primary) == "fastest") ? "" :
2033 TranslateScoresLabel(scores_label(ps_primary))));
2038 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
2040 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2041 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2042 // End of Game Info Section
2044 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2045 if(panel.current_panel_bg != "0")
2046 pos.y += panel_bg_border;
2048 // Draw the scoreboard
2049 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2052 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2056 vector panel_bg_color_save = panel_bg_color;
2057 vector team_score_baseoffset;
2058 vector team_size_baseoffset;
2059 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2061 // put team score to the left of scoreboard (and team size to the right)
2062 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 1.5;
2063 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2064 if(panel.current_panel_bg != "0")
2066 team_score_baseoffset.x -= panel_bg_border;
2067 team_size_baseoffset.x += panel_bg_border;
2072 // put team score to the right of scoreboard (and team size to the left)
2073 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 1.5;
2074 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2075 if(panel.current_panel_bg != "0")
2077 team_score_baseoffset.x += panel_bg_border;
2078 team_size_baseoffset.x -= panel_bg_border;
2082 int team_size_total = 0;
2083 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2085 // calculate team size total (sum of all team sizes)
2086 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2087 if(tm.team != NUM_SPECTATOR)
2088 team_size_total += tm.team_size;
2091 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2093 if(tm.team == NUM_SPECTATOR)
2098 vector rgb = Team_ColorRGB(tm.team);
2099 /*draw_beginBoldFont();
2100 str = ftos(tm.(teamscores(ts_primary)));
2101 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2103 // team score on the left (default)
2104 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 3);
2108 // team score on the right
2109 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 3);
2111 drawstring(str_pos, str, hud_fontsize * 3, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2113 // team size (if set to show on the side)
2114 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2116 // calculate the starting position for the whole team size info string
2117 str = sprintf("%d/%d", tm.team_size, team_size_total);
2118 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2120 // team size on the left
2121 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2125 // team size on the right
2126 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2128 str = sprintf("%d", tm.team_size);
2129 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2130 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2131 str = sprintf("/%d", team_size_total);
2132 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2136 // secondary score, e.g. keyhunt
2137 if(ts_primary != ts_secondary)
2139 str = ftos(tm.(teamscores(ts_secondary)));
2140 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2143 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2148 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2151 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2156 // z411 My team header
2158 drawfill(pos, team_score_size, rgb * 0.5, sbt_highlight_alpha, DRAWFLAG_NORMAL);
2161 str = ftos(tm.(teamscores(ts_primary)));
2163 str_pos.x += (team_score_size.x / 2) - (stringwidth(str, true, team_score_fontsize) / 2);
2164 str_pos.y += (team_score_size.y / 2) - (team_score_fontsize.y / 2);
2166 draw_beginBoldFont();
2167 drawstring(str_pos, str, team_score_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2171 str = Team_CustomName(tm.team);
2173 str_pos.x += team_score_size.x + team_name_fontsize.x * 0.5;
2174 str_pos.y += (team_score_size.y / 2) - (team_name_fontsize.y / 2);
2175 drawcolorcodedstring(str_pos, str, team_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2177 pos.y += team_score_size.y + (hud_fontsize.y * 0.5);
2179 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2180 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2181 else if(panel_bg_color_team > 0)
2182 panel_bg_color = rgb * panel_bg_color_team;
2184 panel_bg_color = rgb;
2185 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2187 panel_bg_color = panel_bg_color_save;
2189 else if(gametype == MAPINFO_TYPE_DUEL)
2191 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2192 if(tm.team != NUM_SPECTATOR)
2195 // z411 make DUEL TABLE
2196 pos = Scoreboard_MakeDuelTable(pos, tm, panel_bg_color, bg_size);
2200 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2201 if(tm.team != NUM_SPECTATOR)
2204 // display it anyway
2205 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2208 pos = Scoreboard_MedalStats_Draw(pos);
2210 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2211 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2213 if(MUTATOR_CALLHOOK(ShowRankings)) {
2214 string ranktitle = M_ARGV(0, string);
2215 if(race_speedaward) {
2216 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);
2217 pos.y += 1.25 * hud_fontsize.y;
2219 if(race_speedaward_alltimebest) {
2220 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);
2221 pos.y += 1.25 * hud_fontsize.y;
2223 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2226 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2229 for(pl = players.sort_next; pl; pl = pl.sort_next)
2231 if(pl.team == NUM_SPECTATOR)
2233 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2234 if(tm.team == NUM_SPECTATOR)
2236 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2237 draw_beginBoldFont();
2238 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2240 pos.y += 1.25 * hud_fontsize.y;
2242 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2243 pos.y += 1.25 * hud_fontsize.y;
2250 // print information about respawn status
2251 float respawn_time = STAT(RESPAWN_TIME);
2255 if(respawn_time < 0)
2257 // a negative number means we are awaiting respawn, time value is still the same
2258 respawn_time *= -1; // remove mark now that we checked it
2260 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2261 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2263 str = sprintf(_("^1Respawning in ^3%s^1..."),
2264 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2265 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2267 count_seconds(ceil(respawn_time - time))
2271 else if(time < respawn_time)
2273 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2274 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2275 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2277 count_seconds(ceil(respawn_time - time))
2281 else if(time >= respawn_time)
2282 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2284 pos.y += 1.2 * hud_fontsize.y;
2285 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2288 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;