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;
56 float sbt_fg_alpha_self;
58 float sbt_highlight_alpha;
59 float sbt_highlight_alpha_self;
61 // provide basic panel cvars to old clients
62 // TODO remove them after a future release (0.8.2+)
63 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
64 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
65 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
66 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
67 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
68 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
69 noref string autocvar_hud_panel_scoreboard_bg_border = "";
70 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
72 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
73 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
74 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
75 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
76 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
77 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
78 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
79 bool autocvar_hud_panel_scoreboard_table_highlight = true;
80 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
81 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
82 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
83 float autocvar_hud_panel_scoreboard_namesize = 15;
84 float autocvar_hud_panel_scoreboard_team_size_position = 0;
86 bool autocvar_hud_panel_scoreboard_accuracy = true;
87 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
88 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
89 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
90 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
92 bool autocvar_hud_panel_scoreboard_dynamichud = false;
94 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
95 bool autocvar_hud_panel_scoreboard_others_showscore = true;
96 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
97 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
98 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
100 // mode 0: returns translated label
101 // mode 1: prints name and description of all the labels
102 string Label_getInfo(string label, int mode)
105 label = "bckills"; // first case in the switch
109 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_HELP(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
110 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_HELP(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
111 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")));
112 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_HELP(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
113 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_HELP(strcat("^3", "deaths", " ^7", _("Number of deaths")));
114 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_HELP(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
115 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_HELP(strcat("^3", "dmg", " ^7", _("The total damage done")));
116 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_HELP(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
117 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_HELP(strcat("^3", "drops", " ^7", _("Number of flag drops")));
118 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_HELP(strcat("^3", "elo", " ^7", _("Player ELO")));
119 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_HELP(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
120 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_HELP(strcat("^3", "faults", " ^7", _("Number of faults committed")));
121 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_HELP(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
122 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_HELP(strcat("^3", "fps", " ^7", _("FPS")));
123 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_HELP(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
124 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_HELP(strcat("^3", "goals", " ^7", _("Number of goals scored")));
125 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_HELP(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
126 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_HELP(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
127 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_HELP(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
128 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_HELP(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
129 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_HELP(strcat("^3", "kills", " ^7", _("Number of kills")));
130 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_HELP(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
131 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_HELP(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
132 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_HELP(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
133 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_HELP(strcat("^3", "name", " ^7", _("Player name")));
134 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_HELP(strcat("^3", "nick", " ^7", _("Player name")));
135 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_HELP(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
136 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")));
137 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_HELP(strcat("^3", "ping", " ^7", _("Ping time")));
138 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_HELP(strcat("^3", "pl", " ^7", _("Packet loss")));
139 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_HELP(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
140 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_HELP(strcat("^3", "rank", " ^7", _("Player rank")));
141 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_HELP(strcat("^3", "returns", " ^7", _("Number of flag returns")));
142 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_HELP(strcat("^3", "revivals", " ^7", _("Number of revivals")));
143 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_HELP(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
144 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_HELP(strcat("^3", "score", " ^7", _("Total score")));
145 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_HELP(strcat("^3", "suicides", " ^7", _("Number of suicides")));
146 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_HELP(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
147 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_HELP(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
148 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_HELP(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
149 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_HELP(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
150 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_HELP(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
151 default: return label;
156 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
157 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
159 void Scoreboard_InitScores()
163 ps_primary = ps_secondary = NULL;
164 ts_primary = ts_secondary = -1;
165 FOREACH(Scores, true, {
166 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
167 if(f == SFL_SORT_PRIO_PRIMARY)
169 if(f == SFL_SORT_PRIO_SECONDARY)
172 if(ps_secondary == NULL)
173 ps_secondary = ps_primary;
175 for(i = 0; i < MAX_TEAMSCORE; ++i)
177 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
178 if(f == SFL_SORT_PRIO_PRIMARY)
180 if(f == SFL_SORT_PRIO_SECONDARY)
183 if(ts_secondary == -1)
184 ts_secondary = ts_primary;
186 Cmd_Scoreboard_SetFields(0);
190 void Scoreboard_UpdatePlayerTeams()
194 for(pl = players.sort_next; pl; pl = pl.sort_next)
197 int Team = entcs_GetScoreTeam(pl.sv_entnum);
198 if(SetTeam(pl, Team))
201 Scoreboard_UpdatePlayerPos(pl);
205 pl = players.sort_next;
210 print(strcat("PNUM: ", ftos(num), "\n"));
215 int Scoreboard_CompareScore(int vl, int vr, int f)
217 TC(int, vl); TC(int, vr); TC(int, f);
218 if(f & SFL_ZERO_IS_WORST)
220 if(vl == 0 && vr != 0)
222 if(vl != 0 && vr == 0)
226 return IS_INCREASING(f);
228 return IS_DECREASING(f);
232 float Scoreboard_ComparePlayerScores(entity left, entity right)
235 vl = entcs_GetTeam(left.sv_entnum);
236 vr = entcs_GetTeam(right.sv_entnum);
248 if(vl == NUM_SPECTATOR)
250 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
252 if(!left.gotscores && right.gotscores)
257 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
261 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
265 FOREACH(Scores, true, {
266 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
267 if (r >= 0) return r;
270 if (left.sv_entnum < right.sv_entnum)
276 void Scoreboard_UpdatePlayerPos(entity player)
279 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
281 SORT_SWAP(player, ent);
283 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
285 SORT_SWAP(ent, player);
289 float Scoreboard_CompareTeamScores(entity left, entity right)
293 if(left.team == NUM_SPECTATOR)
295 if(right.team == NUM_SPECTATOR)
298 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
302 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
306 for(i = 0; i < MAX_TEAMSCORE; ++i)
308 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
313 if (left.team < right.team)
319 void Scoreboard_UpdateTeamPos(entity Team)
322 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
324 SORT_SWAP(Team, ent);
326 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
328 SORT_SWAP(ent, Team);
332 void Cmd_Scoreboard_Help()
334 LOG_HELP(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
335 LOG_HELP(_("Usage:"));
336 LOG_HELP("^2scoreboard_columns_set ^3default");
337 LOG_HELP(_("^2scoreboard_columns_set ^3field1 field2 ..."));
338 LOG_HELP(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
339 LOG_HELP(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
340 LOG_HELP(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
341 LOG_HELP(_("You can use a ^3|^7 to start the right-aligned fields."));
342 LOG_HELP(_("The following field names are recognized (case insensitive):"));
348 LOG_HELP(_("Before a field you can put a + or - sign, then a comma separated list\n"
349 "of game types, then a slash, to make the field show up only in these\n"
350 "or in all but these game types. You can also specify 'all' as a\n"
351 "field to show all fields available for the current game mode."));
354 LOG_HELP(_("The special game type names 'teams' and 'noteams' can be used to\n"
355 "include/exclude ALL teams/noteams game modes."));
358 LOG_HELP(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
359 LOG_HELP(_("will display name, ping and pl aligned to the left, and the fields\n"
360 "right of the vertical bar aligned to the right."));
361 LOG_HELP(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
362 "other gamemodes except DM."));
365 // NOTE: adding a gametype with ? to not warn for an optional field
366 // make sure it's excluded in a previous exclusive rule, if any
367 // otherwise the previous exclusive rule warns anyway
368 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
369 #define SCOREBOARD_DEFAULT_COLUMNS \
370 "ping pl fps name |" \
371 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
372 " -teams,lms/deaths +ft,tdm/deaths" \
374 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
375 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
376 " +tdm,ft,dom,ons,as/teamkills"\
377 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
378 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
379 " +lms/lives +lms/rank" \
380 " +kh/kckills +kh/losses +kh/caps" \
381 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
382 " +as/objectives +nb/faults +nb/goals" \
383 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
384 " +dom/ticks +dom/takes" \
385 " -lms,rc,cts,inv,nb/score"
387 void Cmd_Scoreboard_SetFields(int argc)
392 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
396 return; // do nothing, we don't know gametype and scores yet
398 // sbt_fields uses strunzone on the titles!
399 if(!sbt_field_title[0])
400 for(i = 0; i < MAX_SBT_FIELDS; ++i)
401 sbt_field_title[i] = strzone("(null)");
403 // TODO: re enable with gametype dependant cvars?
404 if(argc < 3) // no arguments provided
405 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
408 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
412 if(argv(2) == "default" || argv(2) == "expand_default")
414 if(argv(2) == "expand_default")
415 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
416 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
418 else if(argv(2) == "all")
420 string s = "ping pl name |"; // scores without a label
421 FOREACH(Scores, true, {
423 if(it != ps_secondary)
424 if(scores_label(it) != "")
425 s = strcat(s, " ", scores_label(it));
427 if(ps_secondary != ps_primary)
428 s = strcat(s, " ", scores_label(ps_secondary));
429 s = strcat(s, " ", scores_label(ps_primary));
430 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
437 hud_fontsize = HUD_GetFontsize("hud_fontsize");
439 duel_score_fontsize = hud_fontsize * 3;
440 duel_name_fontsize = hud_fontsize * 1.5;
441 duel_score_size = vec2(duel_score_fontsize.x * 1.5, duel_score_fontsize.y * 1.25);
443 for(i = 1; i < argc - 1; ++i)
446 bool nocomplain = false;
447 if(substring(str, 0, 1) == "?")
450 str = substring(str, 1, strlen(str) - 1);
453 slash = strstrofs(str, "/", 0);
456 pattern = substring(str, 0, slash);
457 str = substring(str, slash + 1, strlen(str) - (slash + 1));
459 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
463 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
464 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
465 str = strtolower(str);
470 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
471 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
472 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
473 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
474 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
475 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
476 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
477 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
478 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
479 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
482 FOREACH(Scores, true, {
483 if (str == strtolower(scores_label(it))) {
485 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
495 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
499 sbt_field[sbt_num_fields] = j;
502 if(j == ps_secondary)
503 have_secondary = true;
508 if(sbt_num_fields >= MAX_SBT_FIELDS)
512 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
514 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
515 have_secondary = true;
516 if(ps_primary == ps_secondary)
517 have_secondary = true;
518 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
520 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
524 strunzone(sbt_field_title[sbt_num_fields]);
525 for(i = sbt_num_fields; i > 0; --i)
527 sbt_field_title[i] = sbt_field_title[i-1];
528 sbt_field_size[i] = sbt_field_size[i-1];
529 sbt_field[i] = sbt_field[i-1];
531 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
532 sbt_field[0] = SP_NAME;
534 LOG_INFO("fixed missing field 'name'");
538 strunzone(sbt_field_title[sbt_num_fields]);
539 for(i = sbt_num_fields; i > 1; --i)
541 sbt_field_title[i] = sbt_field_title[i-1];
542 sbt_field_size[i] = sbt_field_size[i-1];
543 sbt_field[i] = sbt_field[i-1];
545 sbt_field_title[1] = strzone("|");
546 sbt_field[1] = SP_SEPARATOR;
547 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
549 LOG_INFO("fixed missing field '|'");
552 else if(!have_separator)
554 strcpy(sbt_field_title[sbt_num_fields], "|");
555 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
556 sbt_field[sbt_num_fields] = SP_SEPARATOR;
558 LOG_INFO("fixed missing field '|'");
562 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
563 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
564 sbt_field[sbt_num_fields] = ps_secondary;
566 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
570 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
571 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
572 sbt_field[sbt_num_fields] = ps_primary;
574 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
578 sbt_field[sbt_num_fields] = SP_END;
582 vector sbt_field_rgb;
583 string sbt_field_icon0;
584 string sbt_field_icon1;
585 string sbt_field_icon2;
586 vector sbt_field_icon0_rgb;
587 vector sbt_field_icon1_rgb;
588 vector sbt_field_icon2_rgb;
589 string Scoreboard_GetName(entity pl)
591 if(ready_waiting && pl.ready)
593 sbt_field_icon0 = "gfx/scoreboard/player_ready";
597 int f = entcs_GetClientColors(pl.sv_entnum);
599 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
600 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
601 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
602 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
603 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
606 return entcs_GetName(pl.sv_entnum);
609 string Scoreboard_GetField(entity pl, PlayerScoreField field)
611 float tmp, num, denom;
614 sbt_field_rgb = '1 1 1';
615 sbt_field_icon0 = "";
616 sbt_field_icon1 = "";
617 sbt_field_icon2 = "";
618 sbt_field_icon0_rgb = '1 1 1';
619 sbt_field_icon1_rgb = '1 1 1';
620 sbt_field_icon2_rgb = '1 1 1';
625 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6 (Black Right-Pointing Triangle)
626 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
631 tmp = max(0, min(60, f-20)) / 60; // 20-80 range is green
632 sbt_field_rgb = '0 1 0' + '1 0 1' * tmp;
634 tmp = max(0, min(220, f-80)) / 220; // 80-300 range is red
635 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
642 f = pl.ping_packetloss;
643 tmp = pl.ping_movementloss;
644 if(f == 0 && tmp == 0)
646 str = ftos(ceil(f * 100));
648 str = strcat(str, "~", ftos(ceil(tmp * 100)));
649 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
650 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
654 return Scoreboard_GetName(pl);
657 f = pl.(scores(SP_KILLS));
658 f -= pl.(scores(SP_SUICIDES));
662 num = pl.(scores(SP_KILLS));
663 denom = pl.(scores(SP_DEATHS));
666 sbt_field_rgb = '0 1 0';
667 str = sprintf("%d", num);
668 } else if(num <= 0) {
669 sbt_field_rgb = '1 0 0';
670 str = sprintf("%.1f", num/denom);
672 str = sprintf("%.1f", num/denom);
676 f = pl.(scores(SP_KILLS));
677 f -= pl.(scores(SP_DEATHS));
680 sbt_field_rgb = '0 1 0';
682 sbt_field_rgb = '1 1 1';
684 sbt_field_rgb = '1 0 0';
690 float elo = pl.(scores(SP_ELO));
692 case -1: return "...";
693 case -2: return _("N/A");
694 default: return ftos(elo);
700 float fps = pl.(scores(SP_FPS));
703 sbt_field_rgb = '1 1 1';
704 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
706 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200, true);
707 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
711 case SP_DMG: case SP_DMGTAKEN:
712 return sprintf("%.1f k", pl.(scores(field)) / 1000);
714 default: case SP_SCORE:
715 tmp = pl.(scores(field));
716 f = scores_flags(field);
717 if(field == ps_primary)
718 sbt_field_rgb = '1 1 0';
719 else if(field == ps_secondary)
720 sbt_field_rgb = '0 1 1';
722 sbt_field_rgb = '1 1 1';
723 return ScoreString(f, tmp);
728 float sbt_fixcolumnwidth_len;
729 float sbt_fixcolumnwidth_iconlen;
730 float sbt_fixcolumnwidth_marginlen;
732 string Scoreboard_FixColumnWidth(int i, string str)
738 sbt_fixcolumnwidth_iconlen = 0;
740 if(sbt_field_icon0 != "")
742 sz = draw_getimagesize(sbt_field_icon0);
744 if(sbt_fixcolumnwidth_iconlen < f)
745 sbt_fixcolumnwidth_iconlen = f;
748 if(sbt_field_icon1 != "")
750 sz = draw_getimagesize(sbt_field_icon1);
752 if(sbt_fixcolumnwidth_iconlen < f)
753 sbt_fixcolumnwidth_iconlen = f;
756 if(sbt_field_icon2 != "")
758 sz = draw_getimagesize(sbt_field_icon2);
760 if(sbt_fixcolumnwidth_iconlen < f)
761 sbt_fixcolumnwidth_iconlen = f;
764 if(sbt_fixcolumnwidth_iconlen != 0)
766 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
767 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
770 sbt_fixcolumnwidth_marginlen = 0;
772 if(sbt_field[i] == SP_NAME) // name gets all remaining space
775 float remaining_space = 0;
776 for(j = 0; j < sbt_num_fields; ++j)
778 if (sbt_field[i] != SP_SEPARATOR)
779 remaining_space += sbt_field_size[j] + hud_fontsize.x;
780 sbt_field_size[i] = panel_size.x - remaining_space;
782 if (sbt_fixcolumnwidth_iconlen != 0)
783 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
784 float namesize = panel_size.x - remaining_space;
785 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
786 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
788 max_namesize = vid_conwidth - remaining_space;
791 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
793 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
794 if(sbt_field_size[i] < f)
795 sbt_field_size[i] = f;
800 void Scoreboard_initFieldSizes()
802 for(int i = 0; i < sbt_num_fields; ++i)
804 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
805 Scoreboard_FixColumnWidth(i, "");
809 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
812 vector column_dim = eY * panel_size.y;
814 column_dim.y -= 1.25 * hud_fontsize.y;
815 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
816 pos.x += hud_fontsize.x * 0.5;
817 for(i = 0; i < sbt_num_fields; ++i)
819 if(sbt_field[i] == SP_SEPARATOR)
821 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
824 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
825 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
826 pos.x += column_dim.x;
828 if(sbt_field[i] == SP_SEPARATOR)
830 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
831 for(i = sbt_num_fields - 1; i > 0; --i)
833 if(sbt_field[i] == SP_SEPARATOR)
836 pos.x -= sbt_field_size[i];
841 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
842 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
845 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
846 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
847 pos.x -= hud_fontsize.x;
852 pos.y += 1.25 * hud_fontsize.y;
856 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
858 TC(bool, is_self); TC(int, pl_number);
860 bool is_spec = (entcs_GetSpecState(pl.sv_entnum) == ENTCS_SPEC_PURE);
862 vector h_pos = item_pos;
863 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
864 // alternated rows highlighting
866 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
867 else if((sbt_highlight) && (!(pl_number % 2)))
868 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
870 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
872 vector pos = item_pos;
873 // put a "self indicator" beside the self row, unicode U+25C0 (black left-pointing triangle)
875 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);
877 pos.x += hud_fontsize.x * 0.5;
878 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
879 vector tmp = '0 0 0';
881 PlayerScoreField field;
882 for(i = 0; i < sbt_num_fields; ++i)
884 field = sbt_field[i];
885 if(field == SP_SEPARATOR)
888 if(is_spec && field != SP_NAME && field != SP_PING) {
889 pos.x += sbt_field_size[i] + hud_fontsize.x;
892 str = Scoreboard_GetField(pl, field);
893 str = Scoreboard_FixColumnWidth(i, str);
895 pos.x += sbt_field_size[i] + hud_fontsize.x;
897 if(field == SP_NAME) {
898 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
899 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
901 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
902 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
905 tmp.x = sbt_field_size[i] + hud_fontsize.x;
906 if(sbt_field_icon0 != "")
907 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
908 if(sbt_field_icon1 != "")
909 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
910 if(sbt_field_icon2 != "")
911 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
914 if(sbt_field[i] == SP_SEPARATOR)
916 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
917 for(i = sbt_num_fields-1; i > 0; --i)
919 field = sbt_field[i];
920 if(field == SP_SEPARATOR)
923 if(is_spec && field != SP_NAME && field != SP_PING) {
924 pos.x -= sbt_field_size[i] + hud_fontsize.x;
928 str = Scoreboard_GetField(pl, field);
929 str = Scoreboard_FixColumnWidth(i, str);
931 if(field == SP_NAME) {
932 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
933 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
935 tmp.x = sbt_fixcolumnwidth_len;
936 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
939 tmp.x = sbt_field_size[i];
940 if(sbt_field_icon0 != "")
941 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
942 if(sbt_field_icon1 != "")
943 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
944 if(sbt_field_icon2 != "")
945 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
946 pos.x -= sbt_field_size[i] + hud_fontsize.x;
951 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
954 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
957 vector h_pos = item_pos;
958 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
960 bool complete = (this_team == NUM_SPECTATOR);
963 if((sbt_highlight) && (!(pl_number % 2)))
964 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
966 vector pos = item_pos;
967 pos.x += hud_fontsize.x * 0.5;
968 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
970 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
972 width_limit -= stringwidth("...", false, hud_fontsize);
973 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
974 static float max_name_width = 0;
977 float min_fieldsize = 0;
978 float fieldpadding = hud_fontsize.x * 0.25;
979 if(this_team == NUM_SPECTATOR)
981 if(autocvar_hud_panel_scoreboard_spectators_showping)
982 min_fieldsize = stringwidth("999", false, hud_fontsize);
984 else if(autocvar_hud_panel_scoreboard_others_showscore)
985 min_fieldsize = stringwidth("99", false, hud_fontsize);
986 for(i = 0; pl; pl = pl.sort_next)
988 if(pl.team != this_team)
994 if(this_team == NUM_SPECTATOR)
996 if(autocvar_hud_panel_scoreboard_spectators_showping)
997 field = Scoreboard_GetField(pl, SP_PING);
999 else if(autocvar_hud_panel_scoreboard_others_showscore)
1000 field = Scoreboard_GetField(pl, SP_SCORE);
1002 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
1003 float column_width = stringwidth(str, true, hud_fontsize);
1004 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1006 if(column_width > max_name_width)
1007 max_name_width = column_width;
1008 column_width = max_name_width;
1012 fieldsize = stringwidth(field, false, hud_fontsize);
1013 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1016 if(pos.x + column_width > width_limit)
1021 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1026 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1027 pos.y += hud_fontsize.y * 1.25;
1031 vector name_pos = pos;
1032 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1033 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1034 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1037 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1038 h_size.y = hud_fontsize.y;
1039 vector field_pos = pos;
1040 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1041 field_pos.x += column_width - h_size.x;
1043 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1044 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1045 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1049 h_size.x = column_width + hud_fontsize.x * 0.25;
1050 h_size.y = hud_fontsize.y;
1051 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1053 pos.x += column_width;
1054 pos.x += hud_fontsize.x;
1056 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1059 vector Scoreboard_DrawMedal(vector pos, string icon, float height, float number)
1061 if(!number) return pos;
1062 total_medals += number;
1064 vector tmp_sz, tmp_sz2;
1065 tmp_sz = draw_getimagesize(icon);
1066 tmp_sz2 = vec2(height*(tmp_sz.x/tmp_sz.y), height);
1067 string val = ftos(number);
1069 drawpic(pos, icon, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1071 pos.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1072 drawstring(pos + eY * ((tmp_sz2.y - hud_fontsize.y) / 2), val, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1074 pos.x += stringwidth(val, false, hud_fontsize) + hud_fontsize.x * 0.5;
1078 vector Scoreboard_Duel_DrawPickup(vector pos, bool skinned, string icon, vector sz, float number, bool invert)
1080 vector tmp_in = pos;
1081 vector tmp_sz, tmp_sz2;
1086 picpath = strcat(hud_skin_path, "/", icon);
1087 if(precache_pic(picpath) == "")
1088 picpath = strcat("gfx/hud/default/", icon);
1093 tmp_sz = draw_getimagesize(picpath);
1094 tmp_sz2 = vec2(sz.y*(tmp_sz.x/tmp_sz.y), sz.y);
1096 tmp_in.x = pos.x + ((sz.x - tmp_sz2.x) / 2);
1097 drawpic(tmp_in, picpath, tmp_sz2, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1101 tmp_in.x += tmp_sz2.x + hud_fontsize.x * 0.25;
1103 tmp_in.x -= hud_fontsize.x * 0.25 + hud_fontsize.x;
1104 tmp_in.y += (tmp_sz2.y - hud_fontsize.y) / 2;
1105 drawstring(tmp_in, ftos(number), hud_fontsize, (number ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1107 pos.y += sz.y * 1.1;
1111 void Scoreboard_Duel_DrawTable(vector pos, bool invert, entity pl, entity tm)
1113 vector tmp, tmp_in, tmp_sz, tmp_acc;
1116 float average_acc = 0;
1122 // Stop here if there are no scores available
1123 if(pl.team != tm.team) return;
1126 tmp.x += panel_bg_padding;
1127 tmp.y += panel_bg_padding;
1128 panel_size.x -= panel_bg_padding * 2;
1131 // drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", tmp, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1134 if(invert) { tmp.x += panel_size.x; tmp.x -= duel_score_size.x; }
1135 drawfill(tmp, duel_score_size, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1138 tmp_str = ftos(pl.(scores(SP_SCORE)));
1140 tmp_in.x += (duel_score_size.x / 2) - (stringwidth(tmp_str, true, duel_score_fontsize) / 2);
1141 tmp_in.y += (duel_score_size.y / 2) - (duel_score_fontsize.y / 2);
1143 draw_beginBoldFont();
1144 drawstring(tmp_in, tmp_str, duel_score_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1148 tmp_str = Scoreboard_GetField(pl, SP_NAME);
1151 tmp_in.x -= stringwidth_colors(tmp_str, duel_name_fontsize) + duel_name_fontsize.x * 0.5;
1153 tmp_in.x += duel_score_size.x + duel_name_fontsize.x * 0.5;
1154 tmp_in.y += (duel_score_size.y / 2) - (duel_name_fontsize.y / 2);
1155 drawcolorcodedstring(tmp_in, tmp_str, duel_name_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1158 float column_width = panel_size.x / 5;
1159 tmp.x = pos.x + panel_bg_padding;
1160 tmp.y += hud_fontsize.y * 3 + hud_fontsize.y;
1165 i = (invert ? 4 : 0);
1166 column_dim = vec2(column_width * 4, hud_fontsize.y);
1168 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("kills", false, hud_fontsize) / 2),
1169 "kills", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1170 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("dmg", false, hud_fontsize) / 2),
1171 "dmg", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1172 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("acc", false, hud_fontsize) / 2),
1173 "acc", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1174 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("hits", false, hud_fontsize) / 2),
1175 "hits", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1176 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth("ping", false, hud_fontsize) / 2),
1177 "ping", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1179 tmp.x = pos.x + panel_bg_padding;
1180 tmp.y += hud_fontsize.y;
1183 i = (invert ? 4 : 0);
1185 tmp_str = ftos(pl.(scores(SP_KILLS)));
1186 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1187 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1189 tmp_str = ftos(pl.(scores(SP_DMG)));
1190 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1191 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1193 tmp_acc = tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2);
1200 tmp_str = Scoreboard_GetField(pl, SP_PING);
1201 drawstring(tmp + eX * column_width * (invert ? i-- : i++) + (eX * column_width / 2) - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1202 tmp_str, hud_fontsize * 1.25, sbt_field_rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1204 tmp.x = pos.x + panel_bg_padding;
1205 tmp.y += hud_fontsize.y * 2;
1209 int total_weapons = 0;
1212 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1213 FOREACH(Weapons, it != WEP_Null, {
1214 WepSet set = it.m_wepset;
1215 if (!(weapons_inmap & set))
1217 if (it.spawnflags & WEP_TYPE_OTHER)
1220 int weapon_cnt_fired = pl.accuracy_cnt_fired[i - WEP_FIRST];
1221 int weapon_cnt_hit = pl.accuracy_cnt_hit[i - WEP_FIRST];
1222 int weapon_acc = floor((weapon_cnt_hit / weapon_cnt_fired) * 100);
1223 average_acc += weapon_acc;
1228 int c = (invert ? 4 : 0);
1230 drawfill(tmp_in + eX * column_width * (invert ? 1 : 0), column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1232 draw_str = ftos(pl.accuracy_frags[i - WEP_FIRST]);
1233 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1234 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1236 draw_str = ftos(pl.accuracy_hit[i - WEP_FIRST]);
1237 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1238 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1240 draw_str = sprintf("%d%%", weapon_acc);
1241 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * ((column_width - stringwidth(draw_str, false, hud_fontsize)) / 2),
1242 draw_str, hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1244 draw_str = strcat(ftos(weapon_cnt_hit), " / ", ftos(weapon_cnt_fired));
1245 drawstring(tmp_in + eX * column_width * (invert ? c-- : c++) + eX * (column_width / 2) - eX * stringwidth("36 /", false, hud_fontsize),
1246 draw_str,hud_fontsize, (weapon_cnt_fired ? '1 1 1' : '0.5 0.5 0.5'), panel_fg_alpha, DRAWFLAG_NORMAL);
1250 tmp_in.x = pos.x + panel_size.x - panel_bg_padding - hud_fontsize.x / 2;
1251 drawpic_aspect_skin(tmp_in, it.model2, vec2(50, hud_fontsize.y), '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1254 tmp_in.x = pos.x + panel_bg_padding;
1255 tmp_in.y += hud_fontsize.y * 1.25;
1257 if(weapon_cnt_fired)
1260 average_acc = floor((average_acc / total_weapons) + 0.5);
1262 // draw total accuracy now
1263 tmp_str = sprintf("%d%%", average_acc);
1264 drawstring(tmp_acc - eX * (stringwidth(tmp_str, false, hud_fontsize * 1.25) / 2),
1265 tmp_str, hud_fontsize * 1.25, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1268 vector icon_sz = vec2(column_width, hud_fontsize.y*1.5);
1271 tmp.x += column_width * 4;
1273 drawstring(tmp + eX * ((column_width - stringwidth("medals", false, hud_fontsize)) / 2),
1274 "medals", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1275 tmp.y += hud_fontsize.y * 1.25;
1277 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/humiliation", icon_sz, pl.(scores(SP_MEDAL_HUMILIATION)), invert);
1278 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/impressive", icon_sz, pl.(scores(SP_MEDAL_IMPRESSIVE)), invert);
1279 tmp = Scoreboard_Duel_DrawPickup(tmp, false, "gfx/medal/excellent", icon_sz, pl.(scores(SP_MEDAL_EXCELLENT)), invert);
1282 drawstring(tmp + eX * ((column_width - stringwidth("items", false, hud_fontsize)) / 2),
1283 "items", hud_fontsize, '0.5 0.5 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1284 tmp.y += hud_fontsize.y * 1.25;
1287 it.m_id == ITEM_ArmorMega.m_id ||
1288 it.m_id == ITEM_HealthMega.m_id ||
1289 it.m_id == ITEM_ArmorBig.m_id, {
1290 tmp = Scoreboard_Duel_DrawPickup(tmp, true, it.m_icon, icon_sz, inventoryslots[pl.sv_entnum].inv_items[it.m_id], invert);
1292 if(it.m_id == REGISTRY_MAX(Items))
1296 vector Scoreboard_MakeDuelTable(vector pos, entity tm, vector rgb, vector bg_size)
1298 vector end_pos = pos;
1299 float screen_half = panel_size.x / 2;
1300 float weapon_margin = hud_fontsize.x;
1302 panel_size.x = screen_half - weapon_margin;
1303 panel_size.y = (duel_score_size.y * 5.5);
1305 entity pl_left = players.sort_next;
1306 entity pl_right = pl_left.sort_next;
1308 Scoreboard_Duel_DrawTable(pos, true, pl_left, tm);
1309 Scoreboard_Duel_DrawTable(pos + eX * screen_half + eX * weapon_margin, false, pl_right, tm);
1311 end_pos.y += panel_size.y + (panel_bg_padding * 2);
1312 panel_size.x = screen_half * 2;
1316 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1318 int max_players = 999;
1319 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1321 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1324 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1325 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1326 height /= team_count;
1329 height -= panel_bg_padding * 2; // - padding
1330 max_players = floor(height / (hud_fontsize.y * 1.25));
1331 if(max_players <= 1)
1333 if(max_players == tm.team_size)
1338 entity me = playerslots[current_player];
1340 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1341 panel_size.y += panel_bg_padding * 2;
1344 vector end_pos = panel_pos + eY * (panel_size.y + 0.5* hud_fontsize.y);
1345 if(panel.current_panel_bg != "0")
1346 end_pos.y += panel_bg_border * 2;
1348 if(panel_bg_padding)
1350 panel_pos += '1 1 0' * panel_bg_padding;
1351 panel_size -= '2 2 0' * panel_bg_padding;
1355 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1359 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1361 pos.y += 1.25 * hud_fontsize.y;
1364 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1366 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1369 // print header row and highlight columns
1370 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1372 // fill the table and draw the rows
1373 bool is_self = false;
1374 bool self_shown = false;
1376 for(pl = players.sort_next; pl; pl = pl.sort_next)
1378 if(pl.team != tm.team)
1380 if(i == max_players - 2 && pl != me)
1382 if(!self_shown && me.team == tm.team)
1384 Scoreboard_DrawItem(pos, rgb, me, true, i);
1386 pos.y += 1.25 * hud_fontsize.y;
1390 if(i >= max_players - 1)
1392 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1395 is_self = (pl.sv_entnum == current_player);
1396 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1399 pos.y += 1.25 * hud_fontsize.y;
1403 panel_size.x += panel_bg_padding * 2; // restore initial width
1407 bool Scoreboard_WouldDraw()
1409 if (MUTATOR_CALLHOOK(DrawScoreboard))
1411 else if (QuickMenu_IsOpened())
1413 else if (HUD_Radar_Clickable())
1415 else if (scoreboard_showscores)
1417 else if (intermission == 1)
1419 else if (intermission == 2)
1421 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !MUTATOR_CALLHOOK(DrawDeathScoreboard)
1422 && (!HUD_MinigameMenu_IsOpened() || !active_minigame))
1426 else if (scoreboard_showscores_force)
1431 vector Scoreboard_MedalStats_Draw(vector pos)
1434 float height = hud_fontsize.y * 2;
1436 entity pl = playerslots[current_player];
1438 vector title_pos = pos;
1439 pos.x += 0.5 * hud_fontsize.x + panel_bg_padding;
1440 pos.y += 1.25 * hud_fontsize.y;
1444 pos = Scoreboard_DrawMedal(pos, "gfx/medal/airshot", height, pl.(scores(SP_MEDAL_AIRSHOT)));
1445 pos = Scoreboard_DrawMedal(pos, "gfx/medal/assist", height, pl.(scores(SP_MEDAL_ASSIST)));
1446 pos = Scoreboard_DrawMedal(pos, "gfx/medal/damage", height, pl.(scores(SP_MEDAL_DAMAGE)));
1447 pos = Scoreboard_DrawMedal(pos, "gfx/medal/defense", height, pl.(scores(SP_MEDAL_DEFENSE)));
1448 pos = Scoreboard_DrawMedal(pos, "gfx/medal/electrobitch", height, pl.(scores(SP_MEDAL_ELECTROBITCH)));
1449 pos = Scoreboard_DrawMedal(pos, "gfx/medal/excellent", height, pl.(scores(SP_MEDAL_EXCELLENT)));
1450 pos = Scoreboard_DrawMedal(pos, "gfx/medal/firstblood", height, pl.(scores(SP_MEDAL_FIRSTBLOOD)));
1451 pos = Scoreboard_DrawMedal(pos, "gfx/medal/headshot", height, pl.(scores(SP_MEDAL_HEADSHOT)));
1452 pos = Scoreboard_DrawMedal(pos, "gfx/medal/humiliation", height, pl.(scores(SP_MEDAL_HUMILIATION)));
1453 pos = Scoreboard_DrawMedal(pos, "gfx/medal/impressive", height, pl.(scores(SP_MEDAL_IMPRESSIVE)));
1454 pos = Scoreboard_DrawMedal(pos, "gfx/medal/yoda", height, pl.(scores(SP_MEDAL_YODA)));
1456 if(!total_medals) return orig;
1458 drawstring(title_pos, sprintf(_("Medal stats (total %d)"), total_medals),
1459 hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1462 pos.y += height + hud_fontsize.y * 0.5;
1466 float average_accuracy;
1467 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1471 if (scoreboard_fade_alpha == 1)
1472 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1474 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1476 vector initial_pos = pos;
1478 WepSet weapons_stat = WepSet_GetFromStat();
1479 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1480 int disownedcnt = 0;
1482 FOREACH(Weapons, it != WEP_Null, {
1483 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1485 WepSet set = it.m_wepset;
1486 if(it.spawnflags & WEP_TYPE_OTHER)
1491 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1493 if (it.spawnflags & (WEP_FLAG_HIDDEN | WEP_FLAG_MUTATORBLOCKED | WEP_FLAG_SPECIALATTACK))
1500 int weapon_cnt = (REGISTRY_COUNT(Weapons) - 1) - disownedcnt - nHidden;
1501 if (weapon_cnt <= 0) return pos;
1504 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((REGISTRY_COUNT(Weapons) - nHidden - 1) * 0.5))
1506 int columnns = ceil(weapon_cnt / rows);
1508 float weapon_height = 29;
1509 float height = hud_fontsize.y + weapon_height;
1511 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);
1512 pos.y += 1.25 * hud_fontsize.y;
1513 if(panel.current_panel_bg != "0")
1514 pos.y += panel_bg_border;
1517 panel_size.y = height * rows;
1518 panel_size.y += panel_bg_padding * 2;
1520 float panel_bg_alpha_save = panel_bg_alpha;
1521 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1523 panel_bg_alpha = panel_bg_alpha_save;
1525 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1526 if(panel.current_panel_bg != "0")
1527 end_pos.y += panel_bg_border * 2;
1529 if(panel_bg_padding)
1531 panel_pos += '1 1 0' * panel_bg_padding;
1532 panel_size -= '2 2 0' * panel_bg_padding;
1536 vector tmp = panel_size;
1538 float weapon_width = tmp.x / columnns / rows;
1541 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1545 // column highlighting
1546 for (int i = 0; i < columnns; ++i)
1548 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);
1551 for (int i = 0; i < rows; ++i)
1552 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1555 average_accuracy = 0;
1556 int weapons_with_stats = 0;
1558 pos.x += weapon_width / 2;
1560 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1563 Accuracy_LoadColors();
1565 float oldposx = pos.x;
1569 FOREACH(Weapons, it != WEP_Null, {
1570 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1572 WepSet set = it.m_wepset;
1573 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1575 if (it.spawnflags & WEP_TYPE_OTHER)
1579 if (weapon_stats >= 0)
1580 weapon_alpha = sbt_fg_alpha;
1582 weapon_alpha = 0.2 * sbt_fg_alpha;
1585 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1587 if (weapon_stats >= 0) {
1588 weapons_with_stats += 1;
1589 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1592 s = sprintf("%d%%", weapon_stats * 100);
1595 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1597 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1598 rgb = Accuracy_GetColor(weapon_stats);
1600 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1602 tmpos.x += weapon_width * rows;
1603 pos.x += weapon_width * rows;
1604 if (rows == 2 && column == columnns - 1) {
1612 if (weapons_with_stats)
1613 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1615 panel_size.x += panel_bg_padding * 2; // restore initial width
1617 if (scoreboard_acc_fade_alpha == 1)
1619 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1622 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1624 pos.x += hud_fontsize.x * 0.25;
1625 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1626 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1627 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1629 pos.y += hud_fontsize.y;
1634 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1635 float stat_secrets_found, stat_secrets_total;
1636 float stat_monsters_killed, stat_monsters_total;
1640 // get monster stats
1641 stat_monsters_killed = STAT(MONSTERS_KILLED);
1642 stat_monsters_total = STAT(MONSTERS_TOTAL);
1644 // get secrets stats
1645 stat_secrets_found = STAT(SECRETS_FOUND);
1646 stat_secrets_total = STAT(SECRETS_TOTAL);
1648 // get number of rows
1649 if(stat_secrets_total)
1651 if(stat_monsters_total)
1654 // if no rows, return
1658 // draw table header
1659 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1660 pos.y += 1.25 * hud_fontsize.y;
1661 if(panel.current_panel_bg != "0")
1662 pos.y += panel_bg_border;
1665 panel_size.y = hud_fontsize.y * rows;
1666 panel_size.y += panel_bg_padding * 2;
1669 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1670 if(panel.current_panel_bg != "0")
1671 end_pos.y += panel_bg_border * 2;
1673 if(panel_bg_padding)
1675 panel_pos += '1 1 0' * panel_bg_padding;
1676 panel_size -= '2 2 0' * panel_bg_padding;
1680 vector tmp = panel_size;
1683 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1686 if(stat_monsters_total)
1688 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1689 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1693 if(stat_secrets_total)
1695 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1696 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1699 panel_size.x += panel_bg_padding * 2; // restore initial width
1704 vector Scoreboard_Rankings_Draw(vector pos, string ranktitle, entity pl, vector rgb, vector bg_size)
1707 RANKINGS_RECEIVED_CNT = 0;
1708 for (i=RANKINGS_CNT-1; i>=0; --i)
1710 ++RANKINGS_RECEIVED_CNT;
1712 if (RANKINGS_RECEIVED_CNT == 0)
1715 vector hl_rgb = rgb + '0.5 0.5 0.5';
1717 pos.y += hud_fontsize.y;
1718 drawstring(pos + eX * panel_bg_padding, ranktitle, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1719 pos.y += 1.25 * hud_fontsize.y;
1720 if(panel.current_panel_bg != "0")
1721 pos.y += panel_bg_border;
1726 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1728 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1733 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1735 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1739 float ranksize = 3 * hud_fontsize.x;
1740 float timesize = 5 * hud_fontsize.x;
1741 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1742 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1743 columns = min(columns, RANKINGS_RECEIVED_CNT);
1745 // expand name column to fill the entire row
1746 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1747 namesize += available_space;
1748 columnsize.x += available_space;
1750 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1751 panel_size.y += panel_bg_padding * 2;
1755 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1756 if(panel.current_panel_bg != "0")
1757 end_pos.y += panel_bg_border * 2;
1759 if(panel_bg_padding)
1761 panel_pos += '1 1 0' * panel_bg_padding;
1762 panel_size -= '2 2 0' * panel_bg_padding;
1768 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1770 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1772 int column = 0, j = 0;
1773 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1774 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1781 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1782 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1783 else if(!((j + column) & 1) && sbt_highlight)
1784 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1786 str = count_ordinal(i+1);
1787 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1788 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1789 str = ColorTranslateRGB(grecordholder[i]);
1791 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1792 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1794 pos.y += 1.25 * hud_fontsize.y;
1796 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1800 pos.x += panel_size.x / columns;
1801 pos.y = panel_pos.y;
1804 strfree(zoned_name_self);
1806 panel_size.x += panel_bg_padding * 2; // restore initial width
1810 float scoreboard_time;
1811 bool have_weapon_stats;
1812 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1814 if (MUTATOR_CALLHOOK(DrawScoreboardAccuracy))
1816 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1819 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1820 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1826 if (!have_weapon_stats)
1828 FOREACH(Weapons, it != WEP_Null, {
1829 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1830 if (weapon_stats >= 0)
1832 have_weapon_stats = true;
1836 if (!have_weapon_stats)
1843 void Scoreboard_Draw()
1845 if(!autocvar__hud_configure)
1847 if(!hud_draw_maximized) return;
1849 // frametime checks allow to toggle the scoreboard even when the game is paused
1850 if(scoreboard_active) {
1851 if (scoreboard_fade_alpha < 1)
1852 scoreboard_time = time;
1853 if(hud_configure_menu_open == 1)
1854 scoreboard_fade_alpha = 1;
1855 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1856 if (scoreboard_fadeinspeed && frametime)
1857 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1859 scoreboard_fade_alpha = 1;
1860 if(hud_fontsize_str != autocvar_hud_fontsize)
1862 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1863 Scoreboard_initFieldSizes();
1864 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1868 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1869 if (scoreboard_fadeoutspeed && frametime)
1870 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1872 scoreboard_fade_alpha = 0;
1875 if (!scoreboard_fade_alpha)
1877 scoreboard_acc_fade_alpha = 0;
1882 scoreboard_fade_alpha = 0;
1884 if (autocvar_hud_panel_scoreboard_dynamichud)
1887 HUD_Scale_Disable();
1889 if(scoreboard_fade_alpha <= 0)
1891 panel_fade_alpha *= scoreboard_fade_alpha;
1892 HUD_Panel_LoadCvars();
1894 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1895 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1896 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1897 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1898 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1899 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1901 // don't overlap with con_notify
1902 if(!autocvar__hud_configure)
1903 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1905 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1906 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1907 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1908 panel_size.x = fixed_scoreboard_width;
1910 Scoreboard_UpdatePlayerTeams();
1912 vector pos = panel_pos;
1917 vector sb_gameinfo_type_fontsize, sb_gameinfo_detail_fontsize;
1919 // Begin of Game Info Section
1920 sb_gameinfo_type_fontsize = hud_fontsize * 2.5;
1921 sb_gameinfo_detail_fontsize = hud_fontsize * 1.3;
1924 //drawcolorcodedstring(pos, "bienvenidoainternet.org", sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1925 //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);
1926 //pos.y += sb_gameinfo_type_fontsize.y;
1928 // Game Info: Game Type
1929 str = MapInfo_Type_ToText(gametype);
1931 draw_beginBoldFont();
1932 //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);
1933 drawcolorcodedstring(pos, str, sb_gameinfo_type_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1936 vector tmp_old_sz = draw_getimagesize("gfx/bai_logo");
1937 float tmp_aspect = tmp_old_sz.x/tmp_old_sz.y;
1938 vector tmp_new_sz = vec2(sb_gameinfo_type_fontsize.y * tmp_aspect, sb_gameinfo_type_fontsize.y);
1940 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);
1942 pos.y += sb_gameinfo_type_fontsize.y;
1945 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);
1947 pos.y += sb_gameinfo_detail_fontsize.y;
1949 // Game Info: Game Detail
1950 float tl = STAT(TIMELIMIT);
1951 float fl = STAT(FRAGLIMIT);
1952 float ll = STAT(LEADLIMIT);
1953 float ll_and_fl = STAT(LEADLIMIT_AND_FRAGLIMIT);
1956 str = strcat(str, sprintf(_("^3%1.0f minutes"), tl));
1957 if(!gametype.m_hidelimits)
1962 str = strcat(str, "^7 / "); // delimiter
1965 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(teamscores_flags(ts_primary), fl),
1966 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1967 (teamscores_label(ts_primary) == "fastest") ? "" :
1968 TranslateScoresLabel(teamscores_label(ts_primary))));
1972 str = strcat(str, sprintf(_("^5%s %s"), ScoreString(scores_flags(ps_primary), fl),
1973 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1974 (scores_label(ps_primary) == "fastest") ? "" :
1975 TranslateScoresLabel(scores_label(ps_primary))));
1980 if(tl > 0 || fl > 0)
1983 if (ll_and_fl && fl > 0)
1984 str = strcat(str, "^7 & ");
1986 str = strcat(str, "^7 / ");
1991 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(teamscores_flags(ts_primary), ll),
1992 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1993 (teamscores_label(ts_primary) == "fastest") ? "" :
1994 TranslateScoresLabel(teamscores_label(ts_primary))));
1998 str = strcat(str, sprintf(_("^2+%s %s"), ScoreString(scores_flags(ps_primary), ll),
1999 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
2000 (scores_label(ps_primary) == "fastest") ? "" :
2001 TranslateScoresLabel(scores_label(ps_primary))));
2006 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
2008 str = sprintf(_("^7Map: ^2%s"), shortmapname);
2009 drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
2010 // End of Game Info Section
2012 pos.y += sb_gameinfo_detail_fontsize.y + hud_fontsize.y * 0.3; // space between Game Info Section and score table
2013 if(panel.current_panel_bg != "0")
2014 pos.y += panel_bg_border;
2016 // Draw the scoreboard
2017 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
2020 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
2024 vector panel_bg_color_save = panel_bg_color;
2025 vector team_score_baseoffset;
2026 vector team_size_baseoffset;
2027 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2029 // put team score to the left of scoreboard (and team size to the right)
2030 team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 1.5;
2031 team_size_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 0.5;
2032 if(panel.current_panel_bg != "0")
2034 team_score_baseoffset.x -= panel_bg_border;
2035 team_size_baseoffset.x += panel_bg_border;
2040 // put team score to the right of scoreboard (and team size to the left)
2041 team_score_baseoffset = eY * hud_fontsize.y + eX * hud_fontsize.x * 1.5;
2042 team_size_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
2043 if(panel.current_panel_bg != "0")
2045 team_score_baseoffset.x += panel_bg_border;
2046 team_size_baseoffset.x -= panel_bg_border;
2050 int team_size_total = 0;
2051 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2053 // calculate team size total (sum of all team sizes)
2054 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2055 if(tm.team != NUM_SPECTATOR)
2056 team_size_total += tm.team_size;
2059 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2061 if(tm.team == NUM_SPECTATOR)
2066 draw_beginBoldFont();
2067 vector rgb = Team_ColorRGB(tm.team);
2068 str = ftos(tm.(teamscores(ts_primary)));
2069 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2071 // team score on the left (default)
2072 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 3);
2076 // team score on the right
2077 str_pos = pos + team_score_baseoffset + eX * (panel_size.x + hud_fontsize.x * 3);
2079 drawstring(str_pos, str, hud_fontsize * 3, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2081 // team size (if set to show on the side)
2082 if (autocvar_hud_panel_scoreboard_team_size_position != 0) // team size not off
2084 // calculate the starting position for the whole team size info string
2085 str = sprintf("%d/%d", tm.team_size, team_size_total);
2086 if (autocvar_hud_panel_scoreboard_team_size_position == 1)
2088 // team size on the left
2089 str_pos = pos + team_size_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
2093 // team size on the right
2094 str_pos = pos + team_size_baseoffset + eX * (panel_size.x + hud_fontsize.x * 1.5);
2096 str = sprintf("%d", tm.team_size);
2097 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2098 str_pos += eX * stringwidth(str, true, hud_fontsize * 1.5) + eY * hud_fontsize.y * .5;
2099 str = sprintf("/%d", team_size_total);
2100 drawstring(str_pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2104 // secondary score, e.g. keyhunt
2105 if(ts_primary != ts_secondary)
2107 str = ftos(tm.(teamscores(ts_secondary)));
2108 if (autocvar_hud_panel_scoreboard_team_size_position != 1) // team size not on left
2111 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * -1.5);
2116 str_pos = pos + team_score_baseoffset + vec2(panel_size.x + hud_fontsize.x * 1.5, hud_fontsize.y * 1.5);
2119 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
2122 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
2123 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
2124 else if(panel_bg_color_team > 0)
2125 panel_bg_color = rgb * panel_bg_color_team;
2127 panel_bg_color = rgb;
2128 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2130 panel_bg_color = panel_bg_color_save;
2132 else if(gametype == MAPINFO_TYPE_DUEL)
2134 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2135 if(tm.team != NUM_SPECTATOR)
2138 // z411 make DUEL TABLE
2139 pos = Scoreboard_MakeDuelTable(pos, tm, panel_bg_color, bg_size);
2143 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2144 if(tm.team != NUM_SPECTATOR)
2147 // display it anyway
2148 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
2151 pos = Scoreboard_MedalStats_Draw(pos);
2153 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
2154 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
2156 if(MUTATOR_CALLHOOK(ShowRankings)) {
2157 string ranktitle = M_ARGV(0, string);
2158 if(race_speedaward) {
2159 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);
2160 pos.y += 1.25 * hud_fontsize.y;
2162 if(race_speedaward_alltimebest) {
2163 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);
2164 pos.y += 1.25 * hud_fontsize.y;
2166 pos = Scoreboard_Rankings_Draw(pos, ranktitle, playerslots[player_localnum], panel_bg_color, bg_size);
2169 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
2172 for(pl = players.sort_next; pl; pl = pl.sort_next)
2174 if(pl.team == NUM_SPECTATOR)
2176 for(tm = teams.sort_next; tm; tm = tm.sort_next)
2177 if(tm.team == NUM_SPECTATOR)
2179 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
2180 draw_beginBoldFont();
2181 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
2183 pos.y += 1.25 * hud_fontsize.y;
2185 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
2186 pos.y += 1.25 * hud_fontsize.y;
2193 // print information about respawn status
2194 float respawn_time = STAT(RESPAWN_TIME);
2198 if(respawn_time < 0)
2200 // a negative number means we are awaiting respawn, time value is still the same
2201 respawn_time *= -1; // remove mark now that we checked it
2203 if(respawn_time < time) // it happens for a few frames when server is respawning the player
2204 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
2206 str = sprintf(_("^1Respawning in ^3%s^1..."),
2207 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2208 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2210 count_seconds(ceil(respawn_time - time))
2214 else if(time < respawn_time)
2216 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
2217 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
2218 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
2220 count_seconds(ceil(respawn_time - time))
2224 else if(time >= respawn_time)
2225 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
2227 pos.y += 1.2 * hud_fontsize.y;
2228 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
2231 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;