1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/miscfunctions.qh>
6 #include "quickmenu.qh"
7 #include <common/ent_cs.qh>
8 #include <common/constants.qh>
9 #include <common/net_linked.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/scores.qh>
13 #include <common/stats.qh>
14 #include <common/teams.qh>
18 const int MAX_SBT_FIELDS = MAX_SCORE;
20 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
21 float sbt_field_size[MAX_SBT_FIELDS + 1];
22 string sbt_field_title[MAX_SBT_FIELDS + 1];
25 string autocvar_hud_fontsize;
26 string hud_fontsize_str;
31 float sbt_fg_alpha_self;
33 float sbt_highlight_alpha;
34 float sbt_highlight_alpha_self;
36 // provide basic panel cvars to old clients
37 // TODO remove them after a future release (0.8.2+)
38 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
39 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
40 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
41 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
42 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
43 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
44 noref string autocvar_hud_panel_scoreboard_bg_border = "";
45 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
47 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
48 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
49 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
50 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
51 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
52 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
54 bool autocvar_hud_panel_scoreboard_table_highlight = true;
55 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
57 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
58 float autocvar_hud_panel_scoreboard_namesize = 15;
60 bool autocvar_hud_panel_scoreboard_accuracy = true;
61 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
62 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
63 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
65 bool autocvar_hud_panel_scoreboard_dynamichud = false;
67 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
68 bool autocvar_hud_panel_scoreboard_others_showscore = true;
69 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
70 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
71 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 void drawstringright(vector, string, vector, vector, float, float);
75 void drawstringcenter(vector, string, vector, vector, float, float);
77 // wrapper to put all possible scores titles through gettext
78 string TranslateScoresLabel(string l)
82 case "bckills": return CTX(_("SCO^bckills"));
83 case "bctime": return CTX(_("SCO^bctime"));
84 case "caps": return CTX(_("SCO^caps"));
85 case "captime": return CTX(_("SCO^captime"));
86 case "deaths": return CTX(_("SCO^deaths"));
87 case "destroyed": return CTX(_("SCO^destroyed"));
88 case "dmg": return CTX(_("SCO^damage"));
89 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
90 case "drops": return CTX(_("SCO^drops"));
91 case "faults": return CTX(_("SCO^faults"));
92 case "fckills": return CTX(_("SCO^fckills"));
93 case "goals": return CTX(_("SCO^goals"));
94 case "kckills": return CTX(_("SCO^kckills"));
95 case "kdratio": return CTX(_("SCO^kdratio"));
96 case "kd": return CTX(_("SCO^k/d"));
97 case "kdr": return CTX(_("SCO^kdr"));
98 case "kills": return CTX(_("SCO^kills"));
99 case "teamkills": return CTX(_("SCO^teamkills"));
100 case "laps": return CTX(_("SCO^laps"));
101 case "lives": return CTX(_("SCO^lives"));
102 case "losses": return CTX(_("SCO^losses"));
103 case "name": return CTX(_("SCO^name"));
104 case "sum": return CTX(_("SCO^sum"));
105 case "nick": return CTX(_("SCO^nick"));
106 case "objectives": return CTX(_("SCO^objectives"));
107 case "pickups": return CTX(_("SCO^pickups"));
108 case "ping": return CTX(_("SCO^ping"));
109 case "pl": return CTX(_("SCO^pl"));
110 case "pushes": return CTX(_("SCO^pushes"));
111 case "rank": return CTX(_("SCO^rank"));
112 case "returns": return CTX(_("SCO^returns"));
113 case "revivals": return CTX(_("SCO^revivals"));
114 case "rounds": return CTX(_("SCO^rounds won"));
115 case "score": return CTX(_("SCO^score"));
116 case "suicides": return CTX(_("SCO^suicides"));
117 case "takes": return CTX(_("SCO^takes"));
118 case "ticks": return CTX(_("SCO^ticks"));
123 void Scoreboard_InitScores()
127 ps_primary = ps_secondary = NULL;
128 ts_primary = ts_secondary = -1;
129 FOREACH(Scores, true, {
130 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
131 if(f == SFL_SORT_PRIO_PRIMARY)
133 if(f == SFL_SORT_PRIO_SECONDARY)
136 if(ps_secondary == NULL)
137 ps_secondary = ps_primary;
139 for(i = 0; i < MAX_TEAMSCORE; ++i)
141 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
142 if(f == SFL_SORT_PRIO_PRIMARY)
144 if(f == SFL_SORT_PRIO_SECONDARY)
147 if(ts_secondary == -1)
148 ts_secondary = ts_primary;
150 Cmd_Scoreboard_SetFields(0);
153 float SetTeam(entity pl, float Team);
155 void Scoreboard_UpdatePlayerTeams()
159 for(pl = players.sort_next; pl; pl = pl.sort_next)
162 int Team = entcs_GetScoreTeam(pl.sv_entnum);
163 if(SetTeam(pl, Team))
166 Scoreboard_UpdatePlayerPos(pl);
170 pl = players.sort_next;
175 print(strcat("PNUM: ", ftos(num), "\n"));
180 int Scoreboard_CompareScore(int vl, int vr, int f)
182 TC(int, vl); TC(int, vr); TC(int, f);
183 if(f & SFL_ZERO_IS_WORST)
185 if(vl == 0 && vr != 0)
187 if(vl != 0 && vr == 0)
191 return IS_INCREASING(f);
193 return IS_DECREASING(f);
197 float Scoreboard_ComparePlayerScores(entity left, entity right)
200 vl = entcs_GetTeam(left.sv_entnum);
201 vr = entcs_GetTeam(right.sv_entnum);
213 if(vl == NUM_SPECTATOR)
215 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
217 if(!left.gotscores && right.gotscores)
222 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
226 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
230 FOREACH(Scores, true, {
231 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
232 if (r >= 0) return r;
235 if (left.sv_entnum < right.sv_entnum)
241 void Scoreboard_UpdatePlayerPos(entity player)
244 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
246 SORT_SWAP(player, ent);
248 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
250 SORT_SWAP(ent, player);
254 float Scoreboard_CompareTeamScores(entity left, entity right)
258 if(left.team == NUM_SPECTATOR)
260 if(right.team == NUM_SPECTATOR)
263 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
267 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
271 for(i = 0; i < MAX_TEAMSCORE; ++i)
273 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
278 if (left.team < right.team)
284 void Scoreboard_UpdateTeamPos(entity Team)
287 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
289 SORT_SWAP(Team, ent);
291 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
293 SORT_SWAP(ent, Team);
297 void Cmd_Scoreboard_Help()
299 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
300 LOG_INFO(_("^3|---------------------------------------------------------------|"));
301 LOG_INFO(_("Usage:"));
302 LOG_INFO(_("^2scoreboard_columns_set default"));
303 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
304 LOG_INFO(_("The following field names are recognized (case insensitive):"));
305 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
308 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player"));
309 LOG_INFO(_("^3ping^7 Ping time"));
310 LOG_INFO(_("^3pl^7 Packet loss"));
311 LOG_INFO(_("^3elo^7 Player ELO"));
312 LOG_INFO(_("^3kills^7 Number of kills"));
313 LOG_INFO(_("^3deaths^7 Number of deaths"));
314 LOG_INFO(_("^3suicides^7 Number of suicides"));
315 LOG_INFO(_("^3frags^7 kills - suicides"));
316 LOG_INFO(_("^3teamkills^7 Number of teamkills"));
317 LOG_INFO(_("^3kd^7 The kill-death ratio"));
318 LOG_INFO(_("^3dmg^7 The total damage done"));
319 LOG_INFO(_("^3dmgtaken^7 The total damage taken"));
320 LOG_INFO(_("^3sum^7 frags - deaths"));
321 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured"));
322 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
323 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)"));
324 LOG_INFO(_("^3fckills^7 Number of flag carrier kills"));
325 LOG_INFO(_("^3returns^7 Number of flag returns"));
326 LOG_INFO(_("^3drops^7 Number of flag drops"));
327 LOG_INFO(_("^3lives^7 Number of lives (LMS)"));
328 LOG_INFO(_("^3rank^7 Player rank"));
329 LOG_INFO(_("^3pushes^7 Number of players pushed into void"));
330 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void"));
331 LOG_INFO(_("^3kckills^7 Number of keys carrier kills"));
332 LOG_INFO(_("^3losses^7 Number of times a key was lost"));
333 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)"));
334 LOG_INFO(_("^3time^7 Total time raced (race/cts)"));
335 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)"));
336 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)"));
337 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)"));
338 LOG_INFO(_("^3bckills^7 Number of ball carrier kills"));
339 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway"));
340 LOG_INFO(_("^3score^7 Total score"));
343 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
344 "of game types, then a slash, to make the field show up only in these\n"
345 "or in all but these game types. You can also specify 'all' as a\n"
346 "field to show all fields available for the current game mode.\n\n"));
348 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
349 "include/exclude ALL teams/noteams game modes.\n\n"));
351 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
352 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields"
353 "right of the vertical bar aligned to the right.\n"));
354 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\nother gamemodes except DM.\n"));
357 // NOTE: adding a gametype with ? to not warn for an optional field
358 // make sure it's excluded in a previous exclusive rule, if any
359 // otherwise the previous exclusive rule warns anyway
360 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
361 #define SCOREBOARD_DEFAULT_COLUMNS \
363 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
364 " -teams,lms/deaths +ft,tdm/deaths" \
365 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
366 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
367 " +tdm,ft,dom,ons,as/teamkills"\
368 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
369 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
370 " +lms/lives +lms/rank" \
371 " +kh/caps +kh/pushes +kh/destroyed" \
372 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
373 " +as/objectives +nb/faults +nb/goals" \
374 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
375 " -lms,rc,cts,inv,nb/score"
377 void Cmd_Scoreboard_SetFields(int argc)
382 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
386 return; // do nothing, we don't know gametype and scores yet
388 // sbt_fields uses strunzone on the titles!
389 if(!sbt_field_title[0])
390 for(i = 0; i < MAX_SBT_FIELDS; ++i)
391 sbt_field_title[i] = strzone("(null)");
393 // TODO: re enable with gametype dependant cvars?
394 if(argc < 3) // no arguments provided
395 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
398 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
402 if(argv(2) == "default")
403 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
404 else if(argv(2) == "all")
407 s = "ping pl name |";
408 FOREACH(Scores, true, {
410 if(it != ps_secondary)
411 if(scores_label(it) != "")
412 s = strcat(s, " ", scores_label(it));
414 if(ps_secondary != ps_primary)
415 s = strcat(s, " ", scores_label(ps_secondary));
416 s = strcat(s, " ", scores_label(ps_primary));
417 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
424 hud_fontsize = HUD_GetFontsize("hud_fontsize");
426 for(i = 1; i < argc - 1; ++i)
432 if(substring(str, 0, 1) == "?")
435 str = substring(str, 1, strlen(str) - 1);
438 slash = strstrofs(str, "/", 0);
441 pattern = substring(str, 0, slash);
442 str = substring(str, slash + 1, strlen(str) - (slash + 1));
444 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
448 strunzone(sbt_field_title[sbt_num_fields]);
449 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
450 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
451 str = strtolower(str);
456 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
457 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
458 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
459 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
460 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
461 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
462 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
463 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
464 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
467 FOREACH(Scores, true, {
468 if (str == strtolower(scores_label(it))) {
470 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
480 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
484 sbt_field[sbt_num_fields] = j;
487 if(j == ps_secondary)
488 have_secondary = true;
493 if(sbt_num_fields >= MAX_SBT_FIELDS)
497 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
499 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
500 have_secondary = true;
501 if(ps_primary == ps_secondary)
502 have_secondary = true;
503 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
505 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
509 strunzone(sbt_field_title[sbt_num_fields]);
510 for(i = sbt_num_fields; i > 0; --i)
512 sbt_field_title[i] = sbt_field_title[i-1];
513 sbt_field_size[i] = sbt_field_size[i-1];
514 sbt_field[i] = sbt_field[i-1];
516 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
517 sbt_field[0] = SP_NAME;
519 LOG_INFO("fixed missing field 'name'");
523 strunzone(sbt_field_title[sbt_num_fields]);
524 for(i = sbt_num_fields; i > 1; --i)
526 sbt_field_title[i] = sbt_field_title[i-1];
527 sbt_field_size[i] = sbt_field_size[i-1];
528 sbt_field[i] = sbt_field[i-1];
530 sbt_field_title[1] = strzone("|");
531 sbt_field[1] = SP_SEPARATOR;
532 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
534 LOG_INFO("fixed missing field '|'");
537 else if(!have_separator)
539 strunzone(sbt_field_title[sbt_num_fields]);
540 sbt_field_title[sbt_num_fields] = strzone("|");
541 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
542 sbt_field[sbt_num_fields] = SP_SEPARATOR;
544 LOG_INFO("fixed missing field '|'");
548 strunzone(sbt_field_title[sbt_num_fields]);
549 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
550 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
551 sbt_field[sbt_num_fields] = ps_secondary;
553 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
557 strunzone(sbt_field_title[sbt_num_fields]);
558 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
559 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
560 sbt_field[sbt_num_fields] = ps_primary;
562 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
566 sbt_field[sbt_num_fields] = SP_END;
570 vector sbt_field_rgb;
571 string sbt_field_icon0;
572 string sbt_field_icon1;
573 string sbt_field_icon2;
574 vector sbt_field_icon0_rgb;
575 vector sbt_field_icon1_rgb;
576 vector sbt_field_icon2_rgb;
577 string Scoreboard_GetName(entity pl)
579 if(ready_waiting && pl.ready)
581 sbt_field_icon0 = "gfx/scoreboard/player_ready";
585 int f = entcs_GetClientColors(pl.sv_entnum);
587 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
588 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
589 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
590 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
591 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
594 return entcs_GetName(pl.sv_entnum);
596 string Scoreboard_GetField(entity pl, PlayerScoreField field)
598 float tmp, num, denom;
601 sbt_field_rgb = '1 1 1';
602 sbt_field_icon0 = "";
603 sbt_field_icon1 = "";
604 sbt_field_icon2 = "";
605 sbt_field_icon0_rgb = '1 1 1';
606 sbt_field_icon1_rgb = '1 1 1';
607 sbt_field_icon2_rgb = '1 1 1';
612 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
613 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
617 tmp = max(0, min(220, f-80)) / 220;
618 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
624 f = pl.ping_packetloss;
625 tmp = pl.ping_movementloss;
626 if(f == 0 && tmp == 0)
628 str = ftos(ceil(f * 100));
630 str = strcat(str, "~", ftos(ceil(tmp * 100)));
631 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
632 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
636 return Scoreboard_GetName(pl);
639 f = pl.(scores(SP_KILLS));
640 f -= pl.(scores(SP_SUICIDES));
644 num = pl.(scores(SP_KILLS));
645 denom = pl.(scores(SP_DEATHS));
648 sbt_field_rgb = '0 1 0';
649 str = sprintf("%d", num);
650 } else if(num <= 0) {
651 sbt_field_rgb = '1 0 0';
652 str = sprintf("%.1f", num/denom);
654 str = sprintf("%.1f", num/denom);
658 f = pl.(scores(SP_KILLS));
659 f -= pl.(scores(SP_DEATHS));
662 sbt_field_rgb = '0 1 0';
664 sbt_field_rgb = '1 1 1';
666 sbt_field_rgb = '1 0 0';
672 float elo = pl.(scores(SP_ELO));
674 case -1: return "...";
675 case -2: return _("N/A");
676 default: return ftos(elo);
680 case SP_DMG: case SP_DMGTAKEN:
681 return sprintf("%.1f k", pl.(scores(field)) / 1000);
683 default: case SP_SCORE:
684 tmp = pl.(scores(field));
685 f = scores_flags(field);
686 if(field == ps_primary)
687 sbt_field_rgb = '1 1 0';
688 else if(field == ps_secondary)
689 sbt_field_rgb = '0 1 1';
691 sbt_field_rgb = '1 1 1';
692 return ScoreString(f, tmp);
697 float sbt_fixcolumnwidth_len;
698 float sbt_fixcolumnwidth_iconlen;
699 float sbt_fixcolumnwidth_marginlen;
701 string Scoreboard_FixColumnWidth(int i, string str)
707 sbt_fixcolumnwidth_iconlen = 0;
709 if(sbt_field_icon0 != "")
711 sz = draw_getimagesize(sbt_field_icon0);
713 if(sbt_fixcolumnwidth_iconlen < f)
714 sbt_fixcolumnwidth_iconlen = f;
717 if(sbt_field_icon1 != "")
719 sz = draw_getimagesize(sbt_field_icon1);
721 if(sbt_fixcolumnwidth_iconlen < f)
722 sbt_fixcolumnwidth_iconlen = f;
725 if(sbt_field_icon2 != "")
727 sz = draw_getimagesize(sbt_field_icon2);
729 if(sbt_fixcolumnwidth_iconlen < f)
730 sbt_fixcolumnwidth_iconlen = f;
733 if(sbt_fixcolumnwidth_iconlen != 0)
735 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
736 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
739 sbt_fixcolumnwidth_marginlen = 0;
741 if(sbt_field[i] == SP_NAME) // name gets all remaining space
744 float remaining_space = 0;
745 for(j = 0; j < sbt_num_fields; ++j)
747 if (sbt_field[i] != SP_SEPARATOR)
748 remaining_space += sbt_field_size[j] + hud_fontsize.x;
749 sbt_field_size[i] = panel_size.x - remaining_space;
751 if (sbt_fixcolumnwidth_iconlen != 0)
752 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 float namesize = panel_size.x - remaining_space;
754 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
755 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
757 max_namesize = vid_conwidth - remaining_space;
760 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
762 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
763 if(sbt_field_size[i] < f)
764 sbt_field_size[i] = f;
769 void Scoreboard_initFieldSizes()
771 for(int i = 0; i < sbt_num_fields; ++i)
773 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
774 Scoreboard_FixColumnWidth(i, "");
778 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
781 vector column_dim = eY * panel_size.y;
783 column_dim.y -= 1.25 * hud_fontsize.y;
784 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
785 pos.x += hud_fontsize.x * 0.5;
786 for(i = 0; i < sbt_num_fields; ++i)
788 if(sbt_field[i] == SP_SEPARATOR)
790 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
793 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
794 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
795 pos.x += column_dim.x;
797 if(sbt_field[i] == SP_SEPARATOR)
799 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
800 for(i = sbt_num_fields - 1; i > 0; --i)
802 if(sbt_field[i] == SP_SEPARATOR)
805 pos.x -= sbt_field_size[i];
810 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
811 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
814 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
815 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
816 pos.x -= hud_fontsize.x;
821 pos.y += 1.25 * hud_fontsize.y;
825 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
827 TC(bool, is_self); TC(int, pl_number);
829 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
831 vector h_pos = item_pos;
832 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
833 // alternated rows highlighting
835 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
836 else if((sbt_highlight) && (!(pl_number % 2)))
837 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
839 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
841 vector pos = item_pos;
842 pos.x += hud_fontsize.x * 0.5;
843 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
844 vector tmp = '0 0 0';
846 PlayerScoreField field;
847 for(i = 0; i < sbt_num_fields; ++i)
849 field = sbt_field[i];
850 if(field == SP_SEPARATOR)
853 if(is_spec && field != SP_NAME && field != SP_PING) {
854 pos.x += sbt_field_size[i] + hud_fontsize.x;
857 str = Scoreboard_GetField(pl, field);
858 str = Scoreboard_FixColumnWidth(i, str);
860 pos.x += sbt_field_size[i] + hud_fontsize.x;
862 if(field == SP_NAME) {
863 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
864 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
866 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
867 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
870 tmp.x = sbt_field_size[i] + hud_fontsize.x;
871 if(sbt_field_icon0 != "")
872 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
873 if(sbt_field_icon1 != "")
874 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
875 if(sbt_field_icon2 != "")
876 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
879 if(sbt_field[i] == SP_SEPARATOR)
881 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
882 for(i = sbt_num_fields-1; i > 0; --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;
893 str = Scoreboard_GetField(pl, field);
894 str = Scoreboard_FixColumnWidth(i, str);
896 if(field == SP_NAME) {
897 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
898 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
900 tmp.x = sbt_fixcolumnwidth_len;
901 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
904 tmp.x = sbt_field_size[i];
905 if(sbt_field_icon0 != "")
906 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
907 if(sbt_field_icon1 != "")
908 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 if(sbt_field_icon2 != "")
910 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
911 pos.x -= sbt_field_size[i] + hud_fontsize.x;
916 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
919 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
922 vector h_pos = item_pos;
923 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
925 bool complete = (this_team == NUM_SPECTATOR);
928 if((sbt_highlight) && (!(pl_number % 2)))
929 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
931 vector pos = item_pos;
932 pos.x += hud_fontsize.x * 0.5;
933 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
935 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
937 width_limit -= stringwidth("...", false, hud_fontsize);
938 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
939 static float max_name_width = 0;
942 float min_fieldsize = 0;
943 float fieldpadding = hud_fontsize.x * 0.25;
944 if(this_team == NUM_SPECTATOR)
946 if(autocvar_hud_panel_scoreboard_spectators_showping)
947 min_fieldsize = stringwidth("999", false, hud_fontsize);
949 else if(autocvar_hud_panel_scoreboard_others_showscore)
950 min_fieldsize = stringwidth("99", false, hud_fontsize);
951 for(i = 0; pl; pl = pl.sort_next)
953 if(pl.team != this_team)
959 if(this_team == NUM_SPECTATOR)
961 if(autocvar_hud_panel_scoreboard_spectators_showping)
962 field = Scoreboard_GetField(pl, SP_PING);
964 else if(autocvar_hud_panel_scoreboard_others_showscore)
965 field = Scoreboard_GetField(pl, SP_SCORE);
967 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
968 float column_width = stringwidth(str, true, hud_fontsize);
969 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
971 if(column_width > max_name_width)
972 max_name_width = column_width;
973 column_width = max_name_width;
977 fieldsize = stringwidth(field, false, hud_fontsize);
978 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
981 if(pos.x + column_width > width_limit)
986 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
991 pos.x = item_pos.x + hud_fontsize.x * 0.5;
992 pos.y += hud_fontsize.y * 1.25;
996 vector name_pos = pos;
997 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
998 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
999 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1002 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1003 h_size.y = hud_fontsize.y;
1004 vector field_pos = pos;
1005 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1006 field_pos.x += column_width - h_size.x;
1008 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1009 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1010 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1012 pos.x += column_width;
1013 pos.x += hud_fontsize.x;
1015 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1018 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1020 int max_players = 999;
1021 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1023 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1026 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1027 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1028 height /= team_count;
1031 height -= panel_bg_padding * 2; // - padding
1032 max_players = floor(height / (hud_fontsize.y * 1.25));
1033 if(max_players <= 1)
1035 if(max_players == tm.team_size)
1040 entity me = playerslots[current_player];
1042 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1043 panel_size.y += panel_bg_padding * 2;
1046 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1047 if(panel.current_panel_bg != "0")
1048 end_pos.y += panel_bg_border * 2;
1050 if(panel_bg_padding)
1052 panel_pos += '1 1 0' * panel_bg_padding;
1053 panel_size -= '2 2 0' * panel_bg_padding;
1057 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1061 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1063 pos.y += 1.25 * hud_fontsize.y;
1066 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1068 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1071 // print header row and highlight columns
1072 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1074 // fill the table and draw the rows
1075 bool is_self = false;
1076 bool self_shown = false;
1078 for(pl = players.sort_next; pl; pl = pl.sort_next)
1080 if(pl.team != tm.team)
1082 if(i == max_players - 2 && pl != me)
1084 if(!self_shown && me.team == tm.team)
1086 Scoreboard_DrawItem(pos, rgb, me, true, i);
1088 pos.y += 1.25 * hud_fontsize.y;
1092 if(i >= max_players - 1)
1094 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1097 is_self = (pl.sv_entnum == current_player);
1098 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1101 pos.y += 1.25 * hud_fontsize.y;
1105 panel_size.x += panel_bg_padding * 2; // restore initial width
1109 bool Scoreboard_WouldDraw()
1111 if (MUTATOR_CALLHOOK(DrawScoreboard))
1113 else if (QuickMenu_IsOpened())
1115 else if (HUD_Radar_Clickable())
1117 else if (scoreboard_showscores)
1119 else if (intermission == 1)
1121 else if (intermission == 2)
1123 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1125 else if (scoreboard_showscores_force)
1130 float average_accuracy;
1131 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1133 WepSet weapons_stat = WepSet_GetFromStat();
1134 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1135 int disownedcnt = 0;
1137 FOREACH(Weapons, it != WEP_Null, {
1138 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1140 WepSet set = it.m_wepset;
1141 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1143 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1150 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1151 if (weapon_cnt <= 0) return pos;
1154 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1156 int columnns = ceil(weapon_cnt / rows);
1158 float weapon_height = 29;
1159 float height = hud_fontsize.y + weapon_height;
1161 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1162 pos.y += 1.25 * hud_fontsize.y;
1163 if(panel.current_panel_bg != "0")
1164 pos.y += panel_bg_border;
1167 panel_size.y = height * rows;
1168 panel_size.y += panel_bg_padding * 2;
1171 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1172 if(panel.current_panel_bg != "0")
1173 end_pos.y += panel_bg_border * 2;
1175 if(panel_bg_padding)
1177 panel_pos += '1 1 0' * panel_bg_padding;
1178 panel_size -= '2 2 0' * panel_bg_padding;
1182 vector tmp = panel_size;
1184 float weapon_width = tmp.x / columnns / rows;
1187 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1191 // column highlighting
1192 for (int i = 0; i < columnns; ++i)
1194 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1197 for (int i = 0; i < rows; ++i)
1198 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1201 average_accuracy = 0;
1202 int weapons_with_stats = 0;
1204 pos.x += weapon_width / 2;
1206 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1209 Accuracy_LoadColors();
1211 float oldposx = pos.x;
1215 FOREACH(Weapons, it != WEP_Null, {
1216 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1218 WepSet set = it.m_wepset;
1219 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1223 if (weapon_stats >= 0)
1224 weapon_alpha = sbt_fg_alpha;
1226 weapon_alpha = 0.2 * sbt_fg_alpha;
1229 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1231 if (weapon_stats >= 0) {
1232 weapons_with_stats += 1;
1233 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1236 s = sprintf("%d%%", weapon_stats * 100);
1239 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1241 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1242 rgb = Accuracy_GetColor(weapon_stats);
1244 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1246 tmpos.x += weapon_width * rows;
1247 pos.x += weapon_width * rows;
1248 if (rows == 2 && column == columnns - 1) {
1256 if (weapons_with_stats)
1257 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1259 panel_size.x += panel_bg_padding * 2; // restore initial width
1263 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1265 pos.x += hud_fontsize.x * 0.25;
1266 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1267 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1268 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1270 pos.y += hud_fontsize.y;
1275 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1276 float stat_secrets_found, stat_secrets_total;
1277 float stat_monsters_killed, stat_monsters_total;
1281 // get monster stats
1282 stat_monsters_killed = STAT(MONSTERS_KILLED);
1283 stat_monsters_total = STAT(MONSTERS_TOTAL);
1285 // get secrets stats
1286 stat_secrets_found = STAT(SECRETS_FOUND);
1287 stat_secrets_total = STAT(SECRETS_TOTAL);
1289 // get number of rows
1290 if(stat_secrets_total)
1292 if(stat_monsters_total)
1295 // if no rows, return
1299 // draw table header
1300 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1301 pos.y += 1.25 * hud_fontsize.y;
1302 if(panel.current_panel_bg != "0")
1303 pos.y += panel_bg_border;
1306 panel_size.y = hud_fontsize.y * rows;
1307 panel_size.y += panel_bg_padding * 2;
1310 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1311 if(panel.current_panel_bg != "0")
1312 end_pos.y += panel_bg_border * 2;
1314 if(panel_bg_padding)
1316 panel_pos += '1 1 0' * panel_bg_padding;
1317 panel_size -= '2 2 0' * panel_bg_padding;
1321 vector tmp = panel_size;
1324 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1327 if(stat_monsters_total)
1329 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1330 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1334 if(stat_secrets_total)
1336 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1337 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1340 panel_size.x += panel_bg_padding * 2; // restore initial width
1345 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1348 RANKINGS_RECEIVED_CNT = 0;
1349 for (i=RANKINGS_CNT-1; i>=0; --i)
1351 ++RANKINGS_RECEIVED_CNT;
1353 if (RANKINGS_RECEIVED_CNT == 0)
1356 vector hl_rgb = rgb + '0.5 0.5 0.5';
1358 pos.y += hud_fontsize.y;
1359 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1360 pos.y += 1.25 * hud_fontsize.y;
1361 if(panel.current_panel_bg != "0")
1362 pos.y += panel_bg_border;
1367 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1369 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1374 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1376 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1380 float ranksize = 3 * hud_fontsize.x;
1381 float timesize = 5 * hud_fontsize.x;
1382 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1383 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1384 columns = min(columns, RANKINGS_RECEIVED_CNT);
1386 // expand name column to fill the entire row
1387 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1388 namesize += available_space;
1389 columnsize.x += available_space;
1391 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1392 panel_size.y += panel_bg_padding * 2;
1396 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1397 if(panel.current_panel_bg != "0")
1398 end_pos.y += panel_bg_border * 2;
1400 if(panel_bg_padding)
1402 panel_pos += '1 1 0' * panel_bg_padding;
1403 panel_size -= '2 2 0' * panel_bg_padding;
1409 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1411 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1413 int column = 0, j = 0;
1414 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1421 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1422 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1423 else if(!((j + column) & 1) && sbt_highlight)
1424 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1426 str = count_ordinal(i+1);
1427 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1428 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1429 str = ColorTranslateRGB(grecordholder[i]);
1431 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1432 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1434 pos.y += 1.25 * hud_fontsize.y;
1436 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1440 pos.x += panel_size.x / columns;
1441 pos.y = panel_pos.y;
1445 panel_size.x += panel_bg_padding * 2; // restore initial width
1449 void Scoreboard_Draw()
1451 if(!autocvar__hud_configure)
1453 if(!hud_draw_maximized) return;
1455 // frametime checks allow to toggle the scoreboard even when the game is paused
1456 if(scoreboard_active) {
1457 if(hud_configure_menu_open == 1)
1458 scoreboard_fade_alpha = 1;
1459 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1460 if (scoreboard_fadeinspeed && frametime)
1461 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1463 scoreboard_fade_alpha = 1;
1464 if(hud_fontsize_str != autocvar_hud_fontsize)
1466 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1467 Scoreboard_initFieldSizes();
1468 if(hud_fontsize_str)
1469 strunzone(hud_fontsize_str);
1470 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1474 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1475 if (scoreboard_fadeoutspeed && frametime)
1476 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1478 scoreboard_fade_alpha = 0;
1481 if (!scoreboard_fade_alpha)
1485 scoreboard_fade_alpha = 0;
1487 if (autocvar_hud_panel_scoreboard_dynamichud)
1490 HUD_Scale_Disable();
1492 if(scoreboard_fade_alpha <= 0)
1494 panel_fade_alpha *= scoreboard_fade_alpha;
1495 HUD_Panel_LoadCvars();
1497 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1498 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1499 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1500 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1501 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1502 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1504 // don't overlap with con_notify
1505 if(!autocvar__hud_configure)
1506 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1508 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1509 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1510 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1511 panel_size.x = fixed_scoreboard_width;
1513 Scoreboard_UpdatePlayerTeams();
1515 vector pos = panel_pos;
1521 vector sb_heading_fontsize;
1522 sb_heading_fontsize = hud_fontsize * 2;
1523 draw_beginBoldFont();
1524 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1527 pos.y += sb_heading_fontsize.y;
1528 if(panel.current_panel_bg != "0")
1529 pos.y += panel_bg_border;
1531 // Draw the scoreboard
1532 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1535 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1539 vector panel_bg_color_save = panel_bg_color;
1540 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1541 if(panel.current_panel_bg != "0")
1542 team_score_baseoffset.x -= panel_bg_border;
1543 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1545 if(tm.team == NUM_SPECTATOR)
1550 draw_beginBoldFont();
1551 vector rgb = Team_ColorRGB(tm.team);
1552 str = ftos(tm.(teamscores(ts_primary)));
1553 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1554 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1556 if(ts_primary != ts_secondary)
1558 str = ftos(tm.(teamscores(ts_secondary)));
1559 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1560 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1563 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1564 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1565 else if(panel_bg_color_team > 0)
1566 panel_bg_color = rgb * panel_bg_color_team;
1568 panel_bg_color = rgb;
1569 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1571 panel_bg_color = panel_bg_color_save;
1575 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1576 if(tm.team != NUM_SPECTATOR)
1578 // display it anyway
1579 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1582 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1584 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1585 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1587 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1588 if(race_speedaward) {
1589 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);
1590 pos.y += 1.25 * hud_fontsize.y;
1592 if(race_speedaward_alltimebest) {
1593 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);
1594 pos.y += 1.25 * hud_fontsize.y;
1596 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1599 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1602 for(pl = players.sort_next; pl; pl = pl.sort_next)
1604 if(pl.team == NUM_SPECTATOR)
1606 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1607 if(tm.team == NUM_SPECTATOR)
1609 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1610 draw_beginBoldFont();
1611 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1613 pos.y += 1.25 * hud_fontsize.y;
1615 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1616 pos.y += 1.25 * hud_fontsize.y;
1622 // Print info string
1624 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1625 tl = STAT(TIMELIMIT);
1626 fl = STAT(FRAGLIMIT);
1627 ll = STAT(LEADLIMIT);
1628 if(gametype == MAPINFO_TYPE_LMS)
1631 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1636 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1640 str = strcat(str, _(" or"));
1643 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1644 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1645 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1646 TranslateScoresLabel(teamscores_label(ts_primary))));
1650 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1651 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1652 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1653 TranslateScoresLabel(scores_label(ps_primary))));
1658 if(tl > 0 || fl > 0)
1659 str = strcat(str, _(" or"));
1662 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1663 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1664 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1665 TranslateScoresLabel(teamscores_label(ts_primary))));
1669 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1670 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1671 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1672 TranslateScoresLabel(scores_label(ps_primary))));
1677 pos.y += 1.2 * hud_fontsize.y;
1678 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1680 // print information about respawn status
1681 float respawn_time = STAT(RESPAWN_TIME);
1685 if(respawn_time < 0)
1687 // a negative number means we are awaiting respawn, time value is still the same
1688 respawn_time *= -1; // remove mark now that we checked it
1690 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1691 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1693 str = sprintf(_("^1Respawning in ^3%s^1..."),
1694 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1695 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1697 count_seconds(ceil(respawn_time - time))
1701 else if(time < respawn_time)
1703 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1704 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1705 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1707 count_seconds(ceil(respawn_time - time))
1711 else if(time >= respawn_time)
1712 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1714 pos.y += 1.2 * hud_fontsize.y;
1715 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1718 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;