1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/main.qh>
6 #include <client/miscfunctions.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 const int MAX_SBT_FIELDS = MAX_SCORE;
21 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
22 float sbt_field_size[MAX_SBT_FIELDS + 1];
23 string sbt_field_title[MAX_SBT_FIELDS + 1];
26 string autocvar_hud_fontsize;
27 string hud_fontsize_str;
32 float sbt_fg_alpha_self;
34 float sbt_highlight_alpha;
35 float sbt_highlight_alpha_self;
37 // provide basic panel cvars to old clients
38 // TODO remove them after a future release (0.8.2+)
39 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
40 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
41 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
42 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
43 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
44 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
45 noref string autocvar_hud_panel_scoreboard_bg_border = "";
46 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
48 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
49 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
50 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
51 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
52 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
54 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
55 bool autocvar_hud_panel_scoreboard_table_highlight = true;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
57 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
58 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
59 float autocvar_hud_panel_scoreboard_namesize = 15;
61 bool autocvar_hud_panel_scoreboard_accuracy = true;
62 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
63 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
64 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
65 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
67 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
69 bool autocvar_hud_panel_scoreboard_dynamichud = false;
71 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
72 bool autocvar_hud_panel_scoreboard_others_showscore = true;
73 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
74 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
75 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
77 // mode 0: returns translated label
78 // mode 1: prints name and description of all the labels
79 string Label_getInfo(string label, int mode)
82 label = "bckills"; // first case in the switch
86 case "bckills": if (!mode) return CTX(_("SCO^bckills")); else LOG_INFO(strcat("^3", "bckills", " ^7", _("Number of ball carrier kills")));
87 case "bctime": if (!mode) return CTX(_("SCO^bctime")); else LOG_INFO(strcat("^3", "bctime", " ^7", _("Total amount of time holding the ball in Keepaway")));
88 case "caps": if (!mode) return CTX(_("SCO^caps")); else LOG_INFO(strcat("^3", "caps", " ^7", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
89 case "captime": if (!mode) return CTX(_("SCO^captime")); else LOG_INFO(strcat("^3", "captime", " ^7", _("Time of fastest capture (CTF)")));
90 case "deaths": if (!mode) return CTX(_("SCO^deaths")); else LOG_INFO(strcat("^3", "deaths", " ^7", _("Number of deaths")));
91 case "destroyed": if (!mode) return CTX(_("SCO^destroyed")); else LOG_INFO(strcat("^3", "destroyed", " ^7", _("Number of keys destroyed by pushing them into void")));
92 case "dmg": if (!mode) return CTX(_("SCO^damage")); else LOG_INFO(strcat("^3", "dmg", " ^7", _("The total damage done")));
93 case "dmgtaken": if (!mode) return CTX(_("SCO^dmgtaken")); else LOG_INFO(strcat("^3", "dmgtaken", " ^7", _("The total damage taken")));
94 case "drops": if (!mode) return CTX(_("SCO^drops")); else LOG_INFO(strcat("^3", "drops", " ^7", _("Number of flag drops")));
95 case "elo": if (!mode) return CTX(_("SCO^elo")); else LOG_INFO(strcat("^3", "elo", " ^7", _("Player ELO")));
96 case "fastest": if (!mode) return CTX(_("SCO^fastest")); else LOG_INFO(strcat("^3", "fastest", " ^7", _("Time of fastest lap (Race/CTS)")));
97 case "faults": if (!mode) return CTX(_("SCO^faults")); else LOG_INFO(strcat("^3", "faults", " ^7", _("Number of faults committed")));
98 case "fckills": if (!mode) return CTX(_("SCO^fckills")); else LOG_INFO(strcat("^3", "fckills", " ^7", _("Number of flag carrier kills")));
99 case "fps": if (!mode) return CTX(_("SCO^fps")); else LOG_INFO(strcat("^3", "fps", " ^7", _("FPS")));
100 case "frags": if (!mode) return CTX(_("SCO^frags")); else LOG_INFO(strcat("^3", "frags", " ^7", _("Number of kills minus suicides")));
101 case "goals": if (!mode) return CTX(_("SCO^goals")); else LOG_INFO(strcat("^3", "goals", " ^7", _("Number of goals scored")));
102 case "kckills": if (!mode) return CTX(_("SCO^kckills")); else LOG_INFO(strcat("^3", "kckills", " ^7", _("Number of keys carrier kills")));
103 case "kd": if (!mode) return CTX(_("SCO^k/d")); else LOG_INFO(strcat("^3", "kd", " ^7", _("The kill-death ratio")));
104 case "kdr": if (!mode) return CTX(_("SCO^kdr")); else LOG_INFO(strcat("^3", "kdr", " ^7", _("The kill-death ratio")));
105 case "kdratio": if (!mode) return CTX(_("SCO^kdratio")); else LOG_INFO(strcat("^3", "kdratio", " ^7", _("The kill-death ratio")));
106 case "kills": if (!mode) return CTX(_("SCO^kills")); else LOG_INFO(strcat("^3", "kills", " ^7", _("Number of kills")));
107 case "laps": if (!mode) return CTX(_("SCO^laps")); else LOG_INFO(strcat("^3", "laps", " ^7", _("Number of laps finished (Race/CTS)")));
108 case "lives": if (!mode) return CTX(_("SCO^lives")); else LOG_INFO(strcat("^3", "lives", " ^7", _("Number of lives (LMS)")));
109 case "losses": if (!mode) return CTX(_("SCO^losses")); else LOG_INFO(strcat("^3", "losses", " ^7", _("Number of times a key was lost")));
110 case "name": if (!mode) return CTX(_("SCO^name")); else LOG_INFO(strcat("^3", "name", " ^7", _("Player name")));
111 case "nick": if (!mode) return CTX(_("SCO^nick")); else LOG_INFO(strcat("^3", "nick", " ^7", _("Player name")));
112 case "objectives": if (!mode) return CTX(_("SCO^objectives")); else LOG_INFO(strcat("^3", "objectives", " ^7", _("Number of objectives destroyed")));
113 case "pickups": if (!mode) return CTX(_("SCO^pickups")); else LOG_INFO(strcat("^3", "pickups", " ^7", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
114 case "ping": if (!mode) return CTX(_("SCO^ping")); else LOG_INFO(strcat("^3", "ping", " ^7", _("Ping time")));
115 case "pl": if (!mode) return CTX(_("SCO^pl")); else LOG_INFO(strcat("^3", "pl", " ^7", _("Packet loss")));
116 case "pushes": if (!mode) return CTX(_("SCO^pushes")); else LOG_INFO(strcat("^3", "pushes", " ^7", _("Number of players pushed into void")));
117 case "rank": if (!mode) return CTX(_("SCO^rank")); else LOG_INFO(strcat("^3", "rank", " ^7", _("Player rank")));
118 case "returns": if (!mode) return CTX(_("SCO^returns")); else LOG_INFO(strcat("^3", "returns", " ^7", _("Number of flag returns")));
119 case "revivals": if (!mode) return CTX(_("SCO^revivals")); else LOG_INFO(strcat("^3", "revivals", " ^7", _("Number of revivals")));
120 case "rounds": if (!mode) return CTX(_("SCO^rounds won")); else LOG_INFO(strcat("^3", "rounds", " ^7", _("Number of rounds won")));
121 case "score": if (!mode) return CTX(_("SCO^score")); else LOG_INFO(strcat("^3", "score", " ^7", _("Total score")));
122 case "suicides": if (!mode) return CTX(_("SCO^suicides")); else LOG_INFO(strcat("^3", "suicides", " ^7", _("Number of suicides")));
123 case "sum": if (!mode) return CTX(_("SCO^sum")); else LOG_INFO(strcat("^3", "sum", " ^7", _("Number of kills minus deaths")));
124 case "takes": if (!mode) return CTX(_("SCO^takes")); else LOG_INFO(strcat("^3", "takes", " ^7", _("Number of domination points taken (Domination)")));
125 case "teamkills": if (!mode) return CTX(_("SCO^teamkills")); else LOG_INFO(strcat("^3", "teamkills", " ^7", _("Number of teamkills")));
126 case "ticks": if (!mode) return CTX(_("SCO^ticks")); else LOG_INFO(strcat("^3", "ticks", " ^7", _("Number of ticks (Domination)")));
127 case "time": if (!mode) return CTX(_("SCO^time")); else LOG_INFO(strcat("^3", "time", " ^7", _("Total time raced (Race/CTS)")));
128 default: return label;
133 void PrintScoresLabels() { Label_getInfo(string_null, 1); }
134 string TranslateScoresLabel(string label) { return Label_getInfo(label, 0); }
136 void Scoreboard_InitScores()
140 ps_primary = ps_secondary = NULL;
141 ts_primary = ts_secondary = -1;
142 FOREACH(Scores, true, {
143 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
144 if(f == SFL_SORT_PRIO_PRIMARY)
146 if(f == SFL_SORT_PRIO_SECONDARY)
149 if(ps_secondary == NULL)
150 ps_secondary = ps_primary;
152 for(i = 0; i < MAX_TEAMSCORE; ++i)
154 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
155 if(f == SFL_SORT_PRIO_PRIMARY)
157 if(f == SFL_SORT_PRIO_SECONDARY)
160 if(ts_secondary == -1)
161 ts_secondary = ts_primary;
163 Cmd_Scoreboard_SetFields(0);
167 void Scoreboard_UpdatePlayerTeams()
171 for(pl = players.sort_next; pl; pl = pl.sort_next)
174 int Team = entcs_GetScoreTeam(pl.sv_entnum);
175 if(SetTeam(pl, Team))
178 Scoreboard_UpdatePlayerPos(pl);
182 pl = players.sort_next;
187 print(strcat("PNUM: ", ftos(num), "\n"));
192 int Scoreboard_CompareScore(int vl, int vr, int f)
194 TC(int, vl); TC(int, vr); TC(int, f);
195 if(f & SFL_ZERO_IS_WORST)
197 if(vl == 0 && vr != 0)
199 if(vl != 0 && vr == 0)
203 return IS_INCREASING(f);
205 return IS_DECREASING(f);
209 float Scoreboard_ComparePlayerScores(entity left, entity right)
212 vl = entcs_GetTeam(left.sv_entnum);
213 vr = entcs_GetTeam(right.sv_entnum);
225 if(vl == NUM_SPECTATOR)
227 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
229 if(!left.gotscores && right.gotscores)
234 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
238 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
242 FOREACH(Scores, true, {
243 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
244 if (r >= 0) return r;
247 if (left.sv_entnum < right.sv_entnum)
253 void Scoreboard_UpdatePlayerPos(entity player)
256 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
258 SORT_SWAP(player, ent);
260 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
262 SORT_SWAP(ent, player);
266 float Scoreboard_CompareTeamScores(entity left, entity right)
270 if(left.team == NUM_SPECTATOR)
272 if(right.team == NUM_SPECTATOR)
275 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
279 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
283 for(i = 0; i < MAX_TEAMSCORE; ++i)
285 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
290 if (left.team < right.team)
296 void Scoreboard_UpdateTeamPos(entity Team)
299 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
301 SORT_SWAP(Team, ent);
303 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
305 SORT_SWAP(ent, Team);
309 void Cmd_Scoreboard_Help()
311 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
312 LOG_INFO(_("Usage:"));
313 LOG_INFO("^2scoreboard_columns_set ^3default");
314 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
315 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
316 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
317 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
318 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
319 LOG_INFO(_("The following field names are recognized (case insensitive):"));
325 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
326 "of game types, then a slash, to make the field show up only in these\n"
327 "or in all but these game types. You can also specify 'all' as a\n"
328 "field to show all fields available for the current game mode."));
331 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
332 "include/exclude ALL teams/noteams game modes."));
335 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
336 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
337 "right of the vertical bar aligned to the right."));
338 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
339 "other gamemodes except DM."));
342 // NOTE: adding a gametype with ? to not warn for an optional field
343 // make sure it's excluded in a previous exclusive rule, if any
344 // otherwise the previous exclusive rule warns anyway
345 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
346 #define SCOREBOARD_DEFAULT_COLUMNS \
347 "ping pl fps name |" \
348 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
349 " -teams,lms/deaths +ft,tdm/deaths" \
351 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
352 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
353 " +tdm,ft,dom,ons,as/teamkills"\
354 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
355 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
356 " +lms/lives +lms/rank" \
357 " +kh/kckills +kh/losses +kh/caps" \
358 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
359 " +as/objectives +nb/faults +nb/goals" \
360 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
361 " +dom/ticks +dom/takes" \
362 " -lms,rc,cts,inv,nb/score"
364 void Cmd_Scoreboard_SetFields(int argc)
369 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
373 return; // do nothing, we don't know gametype and scores yet
375 // sbt_fields uses strunzone on the titles!
376 if(!sbt_field_title[0])
377 for(i = 0; i < MAX_SBT_FIELDS; ++i)
378 sbt_field_title[i] = strzone("(null)");
380 // TODO: re enable with gametype dependant cvars?
381 if(argc < 3) // no arguments provided
382 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
385 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
389 if(argv(2) == "default" || argv(2) == "expand_default")
391 if(argv(2) == "expand_default")
392 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
393 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
395 else if(argv(2) == "all")
397 string s = "ping pl name |"; // scores without a label
398 FOREACH(Scores, true, {
400 if(it != ps_secondary)
401 if(scores_label(it) != "")
402 s = strcat(s, " ", scores_label(it));
404 if(ps_secondary != ps_primary)
405 s = strcat(s, " ", scores_label(ps_secondary));
406 s = strcat(s, " ", scores_label(ps_primary));
407 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
414 hud_fontsize = HUD_GetFontsize("hud_fontsize");
416 for(i = 1; i < argc - 1; ++i)
419 bool nocomplain = false;
420 if(substring(str, 0, 1) == "?")
423 str = substring(str, 1, strlen(str) - 1);
426 slash = strstrofs(str, "/", 0);
429 pattern = substring(str, 0, slash);
430 str = substring(str, slash + 1, strlen(str) - (slash + 1));
432 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
436 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
437 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
438 str = strtolower(str);
443 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
444 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
445 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
446 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
447 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
448 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
449 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
450 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
451 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
452 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
455 FOREACH(Scores, true, {
456 if (str == strtolower(scores_label(it))) {
458 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
468 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
472 sbt_field[sbt_num_fields] = j;
475 if(j == ps_secondary)
476 have_secondary = true;
481 if(sbt_num_fields >= MAX_SBT_FIELDS)
485 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
487 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
488 have_secondary = true;
489 if(ps_primary == ps_secondary)
490 have_secondary = true;
491 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
493 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
497 strunzone(sbt_field_title[sbt_num_fields]);
498 for(i = sbt_num_fields; i > 0; --i)
500 sbt_field_title[i] = sbt_field_title[i-1];
501 sbt_field_size[i] = sbt_field_size[i-1];
502 sbt_field[i] = sbt_field[i-1];
504 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
505 sbt_field[0] = SP_NAME;
507 LOG_INFO("fixed missing field 'name'");
511 strunzone(sbt_field_title[sbt_num_fields]);
512 for(i = sbt_num_fields; i > 1; --i)
514 sbt_field_title[i] = sbt_field_title[i-1];
515 sbt_field_size[i] = sbt_field_size[i-1];
516 sbt_field[i] = sbt_field[i-1];
518 sbt_field_title[1] = strzone("|");
519 sbt_field[1] = SP_SEPARATOR;
520 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
522 LOG_INFO("fixed missing field '|'");
525 else if(!have_separator)
527 strcpy(sbt_field_title[sbt_num_fields], "|");
528 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
529 sbt_field[sbt_num_fields] = SP_SEPARATOR;
531 LOG_INFO("fixed missing field '|'");
535 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
536 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
537 sbt_field[sbt_num_fields] = ps_secondary;
539 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
543 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
544 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
545 sbt_field[sbt_num_fields] = ps_primary;
547 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
551 sbt_field[sbt_num_fields] = SP_END;
555 vector sbt_field_rgb;
556 string sbt_field_icon0;
557 string sbt_field_icon1;
558 string sbt_field_icon2;
559 vector sbt_field_icon0_rgb;
560 vector sbt_field_icon1_rgb;
561 vector sbt_field_icon2_rgb;
562 string Scoreboard_GetName(entity pl)
564 if(ready_waiting && pl.ready)
566 sbt_field_icon0 = "gfx/scoreboard/player_ready";
570 int f = entcs_GetClientColors(pl.sv_entnum);
572 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
573 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
574 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
575 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
576 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
579 return entcs_GetName(pl.sv_entnum);
581 string Scoreboard_GetField(entity pl, PlayerScoreField field)
583 float tmp, num, denom;
586 sbt_field_rgb = '1 1 1';
587 sbt_field_icon0 = "";
588 sbt_field_icon1 = "";
589 sbt_field_icon2 = "";
590 sbt_field_icon0_rgb = '1 1 1';
591 sbt_field_icon1_rgb = '1 1 1';
592 sbt_field_icon2_rgb = '1 1 1';
597 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
598 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
602 tmp = max(0, min(220, f-80)) / 220;
603 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
609 f = pl.ping_packetloss;
610 tmp = pl.ping_movementloss;
611 if(f == 0 && tmp == 0)
613 str = ftos(ceil(f * 100));
615 str = strcat(str, "~", ftos(ceil(tmp * 100)));
616 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
617 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
621 return Scoreboard_GetName(pl);
624 f = pl.(scores(SP_KILLS));
625 f -= pl.(scores(SP_SUICIDES));
629 num = pl.(scores(SP_KILLS));
630 denom = pl.(scores(SP_DEATHS));
633 sbt_field_rgb = '0 1 0';
634 str = sprintf("%d", num);
635 } else if(num <= 0) {
636 sbt_field_rgb = '1 0 0';
637 str = sprintf("%.1f", num/denom);
639 str = sprintf("%.1f", num/denom);
643 f = pl.(scores(SP_KILLS));
644 f -= pl.(scores(SP_DEATHS));
647 sbt_field_rgb = '0 1 0';
649 sbt_field_rgb = '1 1 1';
651 sbt_field_rgb = '1 0 0';
657 float elo = pl.(scores(SP_ELO));
659 case -1: return "...";
660 case -2: return _("N/A");
661 default: return ftos(elo);
667 float fps = pl.(scores(SP_FPS));
670 sbt_field_rgb = '1 1 1';
671 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
673 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
674 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
678 case SP_DMG: case SP_DMGTAKEN:
679 return sprintf("%.1f k", pl.(scores(field)) / 1000);
681 default: case SP_SCORE:
682 tmp = pl.(scores(field));
683 f = scores_flags(field);
684 if(field == ps_primary)
685 sbt_field_rgb = '1 1 0';
686 else if(field == ps_secondary)
687 sbt_field_rgb = '0 1 1';
689 sbt_field_rgb = '1 1 1';
690 return ScoreString(f, tmp);
695 float sbt_fixcolumnwidth_len;
696 float sbt_fixcolumnwidth_iconlen;
697 float sbt_fixcolumnwidth_marginlen;
699 string Scoreboard_FixColumnWidth(int i, string str)
705 sbt_fixcolumnwidth_iconlen = 0;
707 if(sbt_field_icon0 != "")
709 sz = draw_getimagesize(sbt_field_icon0);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 if(sbt_field_icon1 != "")
717 sz = draw_getimagesize(sbt_field_icon1);
719 if(sbt_fixcolumnwidth_iconlen < f)
720 sbt_fixcolumnwidth_iconlen = f;
723 if(sbt_field_icon2 != "")
725 sz = draw_getimagesize(sbt_field_icon2);
727 if(sbt_fixcolumnwidth_iconlen < f)
728 sbt_fixcolumnwidth_iconlen = f;
731 if(sbt_fixcolumnwidth_iconlen != 0)
733 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
734 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
737 sbt_fixcolumnwidth_marginlen = 0;
739 if(sbt_field[i] == SP_NAME) // name gets all remaining space
742 float remaining_space = 0;
743 for(j = 0; j < sbt_num_fields; ++j)
745 if (sbt_field[i] != SP_SEPARATOR)
746 remaining_space += sbt_field_size[j] + hud_fontsize.x;
747 sbt_field_size[i] = panel_size.x - remaining_space;
749 if (sbt_fixcolumnwidth_iconlen != 0)
750 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 float namesize = panel_size.x - remaining_space;
752 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
753 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
755 max_namesize = vid_conwidth - remaining_space;
758 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
760 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
761 if(sbt_field_size[i] < f)
762 sbt_field_size[i] = f;
767 void Scoreboard_initFieldSizes()
769 for(int i = 0; i < sbt_num_fields; ++i)
771 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
772 Scoreboard_FixColumnWidth(i, "");
776 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
779 vector column_dim = eY * panel_size.y;
781 column_dim.y -= 1.25 * hud_fontsize.y;
782 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
783 pos.x += hud_fontsize.x * 0.5;
784 for(i = 0; i < sbt_num_fields; ++i)
786 if(sbt_field[i] == SP_SEPARATOR)
788 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
791 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
792 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
793 pos.x += column_dim.x;
795 if(sbt_field[i] == SP_SEPARATOR)
797 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
798 for(i = sbt_num_fields - 1; i > 0; --i)
800 if(sbt_field[i] == SP_SEPARATOR)
803 pos.x -= sbt_field_size[i];
808 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
809 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
812 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
813 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
814 pos.x -= hud_fontsize.x;
819 pos.y += 1.25 * hud_fontsize.y;
823 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
825 TC(bool, is_self); TC(int, pl_number);
827 bool is_spec = entcs_IsSpectating(pl.sv_entnum);
829 vector h_pos = item_pos;
830 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
831 // alternated rows highlighting
833 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
834 else if((sbt_highlight) && (!(pl_number % 2)))
835 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
837 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
839 vector pos = item_pos;
840 pos.x += hud_fontsize.x * 0.5;
841 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
842 vector tmp = '0 0 0';
844 PlayerScoreField field;
845 for(i = 0; i < sbt_num_fields; ++i)
847 field = sbt_field[i];
848 if(field == SP_SEPARATOR)
851 if(is_spec && field != SP_NAME && field != SP_PING) {
852 pos.x += sbt_field_size[i] + hud_fontsize.x;
855 str = Scoreboard_GetField(pl, field);
856 str = Scoreboard_FixColumnWidth(i, str);
858 pos.x += sbt_field_size[i] + hud_fontsize.x;
860 if(field == SP_NAME) {
861 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
862 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
864 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
865 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
868 tmp.x = sbt_field_size[i] + hud_fontsize.x;
869 if(sbt_field_icon0 != "")
870 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
871 if(sbt_field_icon1 != "")
872 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
873 if(sbt_field_icon2 != "")
874 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field[i] == SP_SEPARATOR)
879 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
880 for(i = sbt_num_fields-1; i > 0; --i)
882 field = sbt_field[i];
883 if(field == SP_SEPARATOR)
886 if(is_spec && field != SP_NAME && field != SP_PING) {
887 pos.x -= sbt_field_size[i] + hud_fontsize.x;
891 str = Scoreboard_GetField(pl, field);
892 str = Scoreboard_FixColumnWidth(i, str);
894 if(field == SP_NAME) {
895 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
896 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_fixcolumnwidth_len;
899 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_field_size[i];
903 if(sbt_field_icon0 != "")
904 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
905 if(sbt_field_icon1 != "")
906 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
907 if(sbt_field_icon2 != "")
908 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 pos.x -= sbt_field_size[i] + hud_fontsize.x;
914 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
917 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
920 vector h_pos = item_pos;
921 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
923 bool complete = (this_team == NUM_SPECTATOR);
926 if((sbt_highlight) && (!(pl_number % 2)))
927 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
929 vector pos = item_pos;
930 pos.x += hud_fontsize.x * 0.5;
931 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
933 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
935 width_limit -= stringwidth("...", false, hud_fontsize);
936 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
937 static float max_name_width = 0;
940 float min_fieldsize = 0;
941 float fieldpadding = hud_fontsize.x * 0.25;
942 if(this_team == NUM_SPECTATOR)
944 if(autocvar_hud_panel_scoreboard_spectators_showping)
945 min_fieldsize = stringwidth("999", false, hud_fontsize);
947 else if(autocvar_hud_panel_scoreboard_others_showscore)
948 min_fieldsize = stringwidth("99", false, hud_fontsize);
949 for(i = 0; pl; pl = pl.sort_next)
951 if(pl.team != this_team)
957 if(this_team == NUM_SPECTATOR)
959 if(autocvar_hud_panel_scoreboard_spectators_showping)
960 field = Scoreboard_GetField(pl, SP_PING);
962 else if(autocvar_hud_panel_scoreboard_others_showscore)
963 field = Scoreboard_GetField(pl, SP_SCORE);
965 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
966 float column_width = stringwidth(str, true, hud_fontsize);
967 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
969 if(column_width > max_name_width)
970 max_name_width = column_width;
971 column_width = max_name_width;
975 fieldsize = stringwidth(field, false, hud_fontsize);
976 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
979 if(pos.x + column_width > width_limit)
984 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
989 pos.x = item_pos.x + hud_fontsize.x * 0.5;
990 pos.y += hud_fontsize.y * 1.25;
994 vector name_pos = pos;
995 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
996 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
997 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1000 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1001 h_size.y = hud_fontsize.y;
1002 vector field_pos = pos;
1003 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1004 field_pos.x += column_width - h_size.x;
1006 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1007 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1008 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1012 h_size.x = column_width + hud_fontsize.x * 0.25;
1013 h_size.y = hud_fontsize.y;
1014 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1016 pos.x += column_width;
1017 pos.x += hud_fontsize.x;
1019 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1022 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1024 int max_players = 999;
1025 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1027 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1030 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1031 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1032 height /= team_count;
1035 height -= panel_bg_padding * 2; // - padding
1036 max_players = floor(height / (hud_fontsize.y * 1.25));
1037 if(max_players <= 1)
1039 if(max_players == tm.team_size)
1044 entity me = playerslots[current_player];
1046 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1047 panel_size.y += panel_bg_padding * 2;
1050 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1051 if(panel.current_panel_bg != "0")
1052 end_pos.y += panel_bg_border * 2;
1054 if(panel_bg_padding)
1056 panel_pos += '1 1 0' * panel_bg_padding;
1057 panel_size -= '2 2 0' * panel_bg_padding;
1061 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1065 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1067 pos.y += 1.25 * hud_fontsize.y;
1070 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1072 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1075 // print header row and highlight columns
1076 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1078 // fill the table and draw the rows
1079 bool is_self = false;
1080 bool self_shown = false;
1082 for(pl = players.sort_next; pl; pl = pl.sort_next)
1084 if(pl.team != tm.team)
1086 if(i == max_players - 2 && pl != me)
1088 if(!self_shown && me.team == tm.team)
1090 Scoreboard_DrawItem(pos, rgb, me, true, i);
1092 pos.y += 1.25 * hud_fontsize.y;
1096 if(i >= max_players - 1)
1098 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1101 is_self = (pl.sv_entnum == current_player);
1102 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1105 pos.y += 1.25 * hud_fontsize.y;
1109 panel_size.x += panel_bg_padding * 2; // restore initial width
1113 bool Scoreboard_WouldDraw()
1115 if (MUTATOR_CALLHOOK(DrawScoreboard))
1117 else if (QuickMenu_IsOpened())
1119 else if (HUD_Radar_Clickable())
1121 else if (scoreboard_showscores)
1123 else if (intermission == 1)
1125 else if (intermission == 2)
1127 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
1129 else if (scoreboard_showscores_force)
1134 float average_accuracy;
1135 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1139 if (scoreboard_fade_alpha == 1)
1140 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1142 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1144 vector initial_pos = pos;
1146 WepSet weapons_stat = WepSet_GetFromStat();
1147 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1148 int disownedcnt = 0;
1150 FOREACH(Weapons, it != WEP_Null, {
1151 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1153 WepSet set = it.m_wepset;
1154 if(it.spawnflags & WEP_TYPE_OTHER)
1159 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1161 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1168 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1169 if (weapon_cnt <= 0) return pos;
1172 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1174 int columnns = ceil(weapon_cnt / rows);
1176 float weapon_height = 29;
1177 float height = hud_fontsize.y + weapon_height;
1179 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);
1180 pos.y += 1.25 * hud_fontsize.y;
1181 if(panel.current_panel_bg != "0")
1182 pos.y += panel_bg_border;
1185 panel_size.y = height * rows;
1186 panel_size.y += panel_bg_padding * 2;
1188 float panel_bg_alpha_save = panel_bg_alpha;
1189 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1191 panel_bg_alpha = panel_bg_alpha_save;
1193 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1194 if(panel.current_panel_bg != "0")
1195 end_pos.y += panel_bg_border * 2;
1197 if(panel_bg_padding)
1199 panel_pos += '1 1 0' * panel_bg_padding;
1200 panel_size -= '2 2 0' * panel_bg_padding;
1204 vector tmp = panel_size;
1206 float weapon_width = tmp.x / columnns / rows;
1209 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1213 // column highlighting
1214 for (int i = 0; i < columnns; ++i)
1216 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);
1219 for (int i = 0; i < rows; ++i)
1220 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1223 average_accuracy = 0;
1224 int weapons_with_stats = 0;
1226 pos.x += weapon_width / 2;
1228 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1231 Accuracy_LoadColors();
1233 float oldposx = pos.x;
1237 FOREACH(Weapons, it != WEP_Null, {
1238 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1240 WepSet set = it.m_wepset;
1241 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1243 if (it.spawnflags & WEP_TYPE_OTHER)
1247 if (weapon_stats >= 0)
1248 weapon_alpha = sbt_fg_alpha;
1250 weapon_alpha = 0.2 * sbt_fg_alpha;
1253 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1255 if (weapon_stats >= 0) {
1256 weapons_with_stats += 1;
1257 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1260 s = sprintf("%d%%", weapon_stats * 100);
1263 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1265 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1266 rgb = Accuracy_GetColor(weapon_stats);
1268 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1270 tmpos.x += weapon_width * rows;
1271 pos.x += weapon_width * rows;
1272 if (rows == 2 && column == columnns - 1) {
1280 if (weapons_with_stats)
1281 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1283 panel_size.x += panel_bg_padding * 2; // restore initial width
1285 if (scoreboard_acc_fade_alpha == 1)
1287 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1290 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1292 pos.x += hud_fontsize.x * 0.25;
1293 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1294 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1295 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1297 pos.y += hud_fontsize.y;
1302 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1303 float stat_secrets_found, stat_secrets_total;
1304 float stat_monsters_killed, stat_monsters_total;
1308 // get monster stats
1309 stat_monsters_killed = STAT(MONSTERS_KILLED);
1310 stat_monsters_total = STAT(MONSTERS_TOTAL);
1312 // get secrets stats
1313 stat_secrets_found = STAT(SECRETS_FOUND);
1314 stat_secrets_total = STAT(SECRETS_TOTAL);
1316 // get number of rows
1317 if(stat_secrets_total)
1319 if(stat_monsters_total)
1322 // if no rows, return
1326 // draw table header
1327 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1328 pos.y += 1.25 * hud_fontsize.y;
1329 if(panel.current_panel_bg != "0")
1330 pos.y += panel_bg_border;
1333 panel_size.y = hud_fontsize.y * rows;
1334 panel_size.y += panel_bg_padding * 2;
1337 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1338 if(panel.current_panel_bg != "0")
1339 end_pos.y += panel_bg_border * 2;
1341 if(panel_bg_padding)
1343 panel_pos += '1 1 0' * panel_bg_padding;
1344 panel_size -= '2 2 0' * panel_bg_padding;
1348 vector tmp = panel_size;
1351 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1354 if(stat_monsters_total)
1356 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1357 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1361 if(stat_secrets_total)
1363 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1364 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1367 panel_size.x += panel_bg_padding * 2; // restore initial width
1372 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1375 RANKINGS_RECEIVED_CNT = 0;
1376 for (i=RANKINGS_CNT-1; i>=0; --i)
1378 ++RANKINGS_RECEIVED_CNT;
1380 if (RANKINGS_RECEIVED_CNT == 0)
1383 vector hl_rgb = rgb + '0.5 0.5 0.5';
1385 pos.y += hud_fontsize.y;
1386 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1387 pos.y += 1.25 * hud_fontsize.y;
1388 if(panel.current_panel_bg != "0")
1389 pos.y += panel_bg_border;
1394 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1396 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1401 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1403 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1407 float ranksize = 3 * hud_fontsize.x;
1408 float timesize = 5 * hud_fontsize.x;
1409 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1410 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1411 columns = min(columns, RANKINGS_RECEIVED_CNT);
1413 // expand name column to fill the entire row
1414 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1415 namesize += available_space;
1416 columnsize.x += available_space;
1418 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1419 panel_size.y += panel_bg_padding * 2;
1423 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1424 if(panel.current_panel_bg != "0")
1425 end_pos.y += panel_bg_border * 2;
1427 if(panel_bg_padding)
1429 panel_pos += '1 1 0' * panel_bg_padding;
1430 panel_size -= '2 2 0' * panel_bg_padding;
1436 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1438 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1440 int column = 0, j = 0;
1441 string zoned_name_self = strzone(strdecolorize(entcs_GetName(player_localnum)));
1442 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1449 if(strdecolorize(grecordholder[i]) == zoned_name_self)
1450 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1451 else if(!((j + column) & 1) && sbt_highlight)
1452 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1454 str = count_ordinal(i+1);
1455 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1456 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1457 str = ColorTranslateRGB(grecordholder[i]);
1459 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1460 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1462 pos.y += 1.25 * hud_fontsize.y;
1464 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1468 pos.x += panel_size.x / columns;
1469 pos.y = panel_pos.y;
1472 strfree(zoned_name_self);
1474 panel_size.x += panel_bg_padding * 2; // restore initial width
1478 float scoreboard_time;
1479 bool have_weapon_stats;
1480 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1482 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1484 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1487 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1488 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1494 if (!have_weapon_stats)
1496 FOREACH(Weapons, it != WEP_Null, {
1497 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1498 if (weapon_stats >= 0)
1500 have_weapon_stats = true;
1504 if (!have_weapon_stats)
1511 void Scoreboard_Draw()
1513 if(!autocvar__hud_configure)
1515 if(!hud_draw_maximized) return;
1517 // frametime checks allow to toggle the scoreboard even when the game is paused
1518 if(scoreboard_active) {
1519 if (scoreboard_fade_alpha < 1)
1520 scoreboard_time = time;
1521 if(hud_configure_menu_open == 1)
1522 scoreboard_fade_alpha = 1;
1523 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1524 if (scoreboard_fadeinspeed && frametime)
1525 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1527 scoreboard_fade_alpha = 1;
1528 if(hud_fontsize_str != autocvar_hud_fontsize)
1530 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1531 Scoreboard_initFieldSizes();
1532 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1536 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1537 if (scoreboard_fadeoutspeed && frametime)
1538 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1540 scoreboard_fade_alpha = 0;
1543 if (!scoreboard_fade_alpha)
1545 scoreboard_acc_fade_alpha = 0;
1550 scoreboard_fade_alpha = 0;
1552 if (autocvar_hud_panel_scoreboard_dynamichud)
1555 HUD_Scale_Disable();
1557 if(scoreboard_fade_alpha <= 0)
1559 panel_fade_alpha *= scoreboard_fade_alpha;
1560 HUD_Panel_LoadCvars();
1562 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1563 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1564 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1565 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1566 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1567 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1569 // don't overlap with con_notify
1570 if(!autocvar__hud_configure)
1571 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1573 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1574 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1575 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1576 panel_size.x = fixed_scoreboard_width;
1578 Scoreboard_UpdatePlayerTeams();
1580 vector pos = panel_pos;
1586 vector sb_heading_fontsize;
1587 sb_heading_fontsize = hud_fontsize * 2;
1588 draw_beginBoldFont();
1589 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1592 pos.y += sb_heading_fontsize.y;
1593 if(panel.current_panel_bg != "0")
1594 pos.y += panel_bg_border;
1596 // Draw the scoreboard
1597 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1600 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1604 vector panel_bg_color_save = panel_bg_color;
1605 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1606 if(panel.current_panel_bg != "0")
1607 team_score_baseoffset.x -= panel_bg_border;
1608 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1610 if(tm.team == NUM_SPECTATOR)
1615 draw_beginBoldFont();
1616 vector rgb = Team_ColorRGB(tm.team);
1617 str = ftos(tm.(teamscores(ts_primary)));
1618 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1619 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1621 if(ts_primary != ts_secondary)
1623 str = ftos(tm.(teamscores(ts_secondary)));
1624 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1625 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1628 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1629 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1630 else if(panel_bg_color_team > 0)
1631 panel_bg_color = rgb * panel_bg_color_team;
1633 panel_bg_color = rgb;
1634 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1636 panel_bg_color = panel_bg_color_save;
1640 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1641 if(tm.team != NUM_SPECTATOR)
1643 // display it anyway
1644 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1647 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1648 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1650 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1651 if(race_speedaward) {
1652 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);
1653 pos.y += 1.25 * hud_fontsize.y;
1655 if(race_speedaward_alltimebest) {
1656 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);
1657 pos.y += 1.25 * hud_fontsize.y;
1659 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1662 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1665 for(pl = players.sort_next; pl; pl = pl.sort_next)
1667 if(pl.team == NUM_SPECTATOR)
1669 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1670 if(tm.team == NUM_SPECTATOR)
1672 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1673 draw_beginBoldFont();
1674 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1676 pos.y += 1.25 * hud_fontsize.y;
1678 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1679 pos.y += 1.25 * hud_fontsize.y;
1685 // Print info string
1687 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1688 tl = STAT(TIMELIMIT);
1689 fl = STAT(FRAGLIMIT);
1690 ll = STAT(LEADLIMIT);
1694 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1699 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1703 str = strcat(str, _(" or"));
1706 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1707 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1708 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1709 TranslateScoresLabel(teamscores_label(ts_primary))));
1713 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1714 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1715 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1716 TranslateScoresLabel(scores_label(ps_primary))));
1721 if(tl > 0 || fl > 0)
1722 str = strcat(str, _(" or"));
1725 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1726 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1727 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1728 TranslateScoresLabel(teamscores_label(ts_primary))));
1732 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1733 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1734 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1735 TranslateScoresLabel(scores_label(ps_primary))));
1740 pos.y += 1.2 * hud_fontsize.y;
1741 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1743 // print information about respawn status
1744 float respawn_time = STAT(RESPAWN_TIME);
1748 if(respawn_time < 0)
1750 // a negative number means we are awaiting respawn, time value is still the same
1751 respawn_time *= -1; // remove mark now that we checked it
1753 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1754 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1756 str = sprintf(_("^1Respawning in ^3%s^1..."),
1757 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1758 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1760 count_seconds(ceil(respawn_time - time))
1764 else if(time < respawn_time)
1766 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1767 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1768 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1770 count_seconds(ceil(respawn_time - time))
1774 else if(time >= respawn_time)
1775 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1777 pos.y += 1.2 * hud_fontsize.y;
1778 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1781 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;