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" \
366 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
367 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
368 " +tdm,ft,dom,ons,as/teamkills"\
369 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
370 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
371 " +lms/lives +lms/rank" \
372 " +kh/kckills +kh/losses +kh/caps" \
373 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
374 " +as/objectives +nb/faults +nb/goals" \
375 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
376 " +dom/ticks +dom/takes" \
377 " -lms,rc,cts,inv,nb/score"
379 void Cmd_Scoreboard_SetFields(int argc)
384 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
388 return; // do nothing, we don't know gametype and scores yet
390 // sbt_fields uses strunzone on the titles!
391 if(!sbt_field_title[0])
392 for(i = 0; i < MAX_SBT_FIELDS; ++i)
393 sbt_field_title[i] = strzone("(null)");
395 // TODO: re enable with gametype dependant cvars?
396 if(argc < 3) // no arguments provided
397 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
400 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
404 if(argv(2) == "default")
405 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
406 else if(argv(2) == "all")
409 s = "ping pl name |";
410 FOREACH(Scores, true, {
412 if(it != ps_secondary)
413 if(scores_label(it) != "")
414 s = strcat(s, " ", scores_label(it));
416 if(ps_secondary != ps_primary)
417 s = strcat(s, " ", scores_label(ps_secondary));
418 s = strcat(s, " ", scores_label(ps_primary));
419 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
426 hud_fontsize = HUD_GetFontsize("hud_fontsize");
428 for(i = 1; i < argc - 1; ++i)
434 if(substring(str, 0, 1) == "?")
437 str = substring(str, 1, strlen(str) - 1);
440 slash = strstrofs(str, "/", 0);
443 pattern = substring(str, 0, slash);
444 str = substring(str, slash + 1, strlen(str) - (slash + 1));
446 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
450 strunzone(sbt_field_title[sbt_num_fields]);
451 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
452 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
453 str = strtolower(str);
458 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
459 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
460 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
461 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
462 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
463 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
464 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
465 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
466 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
469 FOREACH(Scores, true, {
470 if (str == strtolower(scores_label(it))) {
472 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
482 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
486 sbt_field[sbt_num_fields] = j;
489 if(j == ps_secondary)
490 have_secondary = true;
495 if(sbt_num_fields >= MAX_SBT_FIELDS)
499 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
501 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
502 have_secondary = true;
503 if(ps_primary == ps_secondary)
504 have_secondary = true;
505 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
507 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
511 strunzone(sbt_field_title[sbt_num_fields]);
512 for(i = sbt_num_fields; i > 0; --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[0] = strzone(TranslateScoresLabel("name"));
519 sbt_field[0] = SP_NAME;
521 LOG_INFO("fixed missing field 'name'");
525 strunzone(sbt_field_title[sbt_num_fields]);
526 for(i = sbt_num_fields; i > 1; --i)
528 sbt_field_title[i] = sbt_field_title[i-1];
529 sbt_field_size[i] = sbt_field_size[i-1];
530 sbt_field[i] = sbt_field[i-1];
532 sbt_field_title[1] = strzone("|");
533 sbt_field[1] = SP_SEPARATOR;
534 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
536 LOG_INFO("fixed missing field '|'");
539 else if(!have_separator)
541 strunzone(sbt_field_title[sbt_num_fields]);
542 sbt_field_title[sbt_num_fields] = strzone("|");
543 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
544 sbt_field[sbt_num_fields] = SP_SEPARATOR;
546 LOG_INFO("fixed missing field '|'");
550 strunzone(sbt_field_title[sbt_num_fields]);
551 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
552 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
553 sbt_field[sbt_num_fields] = ps_secondary;
555 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
559 strunzone(sbt_field_title[sbt_num_fields]);
560 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
561 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
562 sbt_field[sbt_num_fields] = ps_primary;
564 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
568 sbt_field[sbt_num_fields] = SP_END;
572 vector sbt_field_rgb;
573 string sbt_field_icon0;
574 string sbt_field_icon1;
575 string sbt_field_icon2;
576 vector sbt_field_icon0_rgb;
577 vector sbt_field_icon1_rgb;
578 vector sbt_field_icon2_rgb;
579 string Scoreboard_GetName(entity pl)
581 if(ready_waiting && pl.ready)
583 sbt_field_icon0 = "gfx/scoreboard/player_ready";
587 int f = entcs_GetClientColors(pl.sv_entnum);
589 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
590 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
591 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
592 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
593 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
596 return entcs_GetName(pl.sv_entnum);
598 string Scoreboard_GetField(entity pl, PlayerScoreField field)
600 float tmp, num, denom;
603 sbt_field_rgb = '1 1 1';
604 sbt_field_icon0 = "";
605 sbt_field_icon1 = "";
606 sbt_field_icon2 = "";
607 sbt_field_icon0_rgb = '1 1 1';
608 sbt_field_icon1_rgb = '1 1 1';
609 sbt_field_icon2_rgb = '1 1 1';
614 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
615 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
619 tmp = max(0, min(220, f-80)) / 220;
620 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
626 f = pl.ping_packetloss;
627 tmp = pl.ping_movementloss;
628 if(f == 0 && tmp == 0)
630 str = ftos(ceil(f * 100));
632 str = strcat(str, "~", ftos(ceil(tmp * 100)));
633 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
634 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
638 return Scoreboard_GetName(pl);
641 f = pl.(scores(SP_KILLS));
642 f -= pl.(scores(SP_SUICIDES));
646 num = pl.(scores(SP_KILLS));
647 denom = pl.(scores(SP_DEATHS));
650 sbt_field_rgb = '0 1 0';
651 str = sprintf("%d", num);
652 } else if(num <= 0) {
653 sbt_field_rgb = '1 0 0';
654 str = sprintf("%.1f", num/denom);
656 str = sprintf("%.1f", num/denom);
660 f = pl.(scores(SP_KILLS));
661 f -= pl.(scores(SP_DEATHS));
664 sbt_field_rgb = '0 1 0';
666 sbt_field_rgb = '1 1 1';
668 sbt_field_rgb = '1 0 0';
674 float elo = pl.(scores(SP_ELO));
676 case -1: return "...";
677 case -2: return _("N/A");
678 default: return ftos(elo);
682 case SP_DMG: case SP_DMGTAKEN:
683 return sprintf("%.1f k", pl.(scores(field)) / 1000);
685 default: case SP_SCORE:
686 tmp = pl.(scores(field));
687 f = scores_flags(field);
688 if(field == ps_primary)
689 sbt_field_rgb = '1 1 0';
690 else if(field == ps_secondary)
691 sbt_field_rgb = '0 1 1';
693 sbt_field_rgb = '1 1 1';
694 return ScoreString(f, tmp);
699 float sbt_fixcolumnwidth_len;
700 float sbt_fixcolumnwidth_iconlen;
701 float sbt_fixcolumnwidth_marginlen;
703 string Scoreboard_FixColumnWidth(int i, string str)
709 sbt_fixcolumnwidth_iconlen = 0;
711 if(sbt_field_icon0 != "")
713 sz = draw_getimagesize(sbt_field_icon0);
715 if(sbt_fixcolumnwidth_iconlen < f)
716 sbt_fixcolumnwidth_iconlen = f;
719 if(sbt_field_icon1 != "")
721 sz = draw_getimagesize(sbt_field_icon1);
723 if(sbt_fixcolumnwidth_iconlen < f)
724 sbt_fixcolumnwidth_iconlen = f;
727 if(sbt_field_icon2 != "")
729 sz = draw_getimagesize(sbt_field_icon2);
731 if(sbt_fixcolumnwidth_iconlen < f)
732 sbt_fixcolumnwidth_iconlen = f;
735 if(sbt_fixcolumnwidth_iconlen != 0)
737 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
738 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
741 sbt_fixcolumnwidth_marginlen = 0;
743 if(sbt_field[i] == SP_NAME) // name gets all remaining space
746 float remaining_space = 0;
747 for(j = 0; j < sbt_num_fields; ++j)
749 if (sbt_field[i] != SP_SEPARATOR)
750 remaining_space += sbt_field_size[j] + hud_fontsize.x;
751 sbt_field_size[i] = panel_size.x - remaining_space;
753 if (sbt_fixcolumnwidth_iconlen != 0)
754 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
755 float namesize = panel_size.x - remaining_space;
756 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
757 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
759 max_namesize = vid_conwidth - remaining_space;
762 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
764 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
765 if(sbt_field_size[i] < f)
766 sbt_field_size[i] = f;
771 void Scoreboard_initFieldSizes()
773 for(int i = 0; i < sbt_num_fields; ++i)
775 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
776 Scoreboard_FixColumnWidth(i, "");
780 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
783 vector column_dim = eY * panel_size.y;
785 column_dim.y -= 1.25 * hud_fontsize.y;
786 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
787 pos.x += hud_fontsize.x * 0.5;
788 for(i = 0; i < sbt_num_fields; ++i)
790 if(sbt_field[i] == SP_SEPARATOR)
792 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
795 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
796 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
797 pos.x += column_dim.x;
799 if(sbt_field[i] == SP_SEPARATOR)
801 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
802 for(i = sbt_num_fields - 1; i > 0; --i)
804 if(sbt_field[i] == SP_SEPARATOR)
807 pos.x -= sbt_field_size[i];
812 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
813 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
816 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
817 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
818 pos.x -= hud_fontsize.x;
823 pos.y += 1.25 * hud_fontsize.y;
827 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
829 TC(bool, is_self); TC(int, pl_number);
831 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
833 vector h_pos = item_pos;
834 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
835 // alternated rows highlighting
837 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
838 else if((sbt_highlight) && (!(pl_number % 2)))
839 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
841 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
843 vector pos = item_pos;
844 pos.x += hud_fontsize.x * 0.5;
845 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
846 vector tmp = '0 0 0';
848 PlayerScoreField field;
849 for(i = 0; i < sbt_num_fields; ++i)
851 field = sbt_field[i];
852 if(field == SP_SEPARATOR)
855 if(is_spec && field != SP_NAME && field != SP_PING) {
856 pos.x += sbt_field_size[i] + hud_fontsize.x;
859 str = Scoreboard_GetField(pl, field);
860 str = Scoreboard_FixColumnWidth(i, str);
862 pos.x += sbt_field_size[i] + hud_fontsize.x;
864 if(field == SP_NAME) {
865 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
866 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
868 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
869 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
872 tmp.x = sbt_field_size[i] + hud_fontsize.x;
873 if(sbt_field_icon0 != "")
874 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
875 if(sbt_field_icon1 != "")
876 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field_icon2 != "")
878 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
881 if(sbt_field[i] == SP_SEPARATOR)
883 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
884 for(i = sbt_num_fields-1; i > 0; --i)
886 field = sbt_field[i];
887 if(field == SP_SEPARATOR)
890 if(is_spec && field != SP_NAME && field != SP_PING) {
891 pos.x -= sbt_field_size[i] + hud_fontsize.x;
895 str = Scoreboard_GetField(pl, field);
896 str = Scoreboard_FixColumnWidth(i, str);
898 if(field == SP_NAME) {
899 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
900 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_fixcolumnwidth_len;
903 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 tmp.x = sbt_field_size[i];
907 if(sbt_field_icon0 != "")
908 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 if(sbt_field_icon1 != "")
910 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
911 if(sbt_field_icon2 != "")
912 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
913 pos.x -= sbt_field_size[i] + hud_fontsize.x;
918 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
921 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
924 vector h_pos = item_pos;
925 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
927 bool complete = (this_team == NUM_SPECTATOR);
930 if((sbt_highlight) && (!(pl_number % 2)))
931 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
933 vector pos = item_pos;
934 pos.x += hud_fontsize.x * 0.5;
935 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
937 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
939 width_limit -= stringwidth("...", false, hud_fontsize);
940 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
941 static float max_name_width = 0;
944 float min_fieldsize = 0;
945 float fieldpadding = hud_fontsize.x * 0.25;
946 if(this_team == NUM_SPECTATOR)
948 if(autocvar_hud_panel_scoreboard_spectators_showping)
949 min_fieldsize = stringwidth("999", false, hud_fontsize);
951 else if(autocvar_hud_panel_scoreboard_others_showscore)
952 min_fieldsize = stringwidth("99", false, hud_fontsize);
953 for(i = 0; pl; pl = pl.sort_next)
955 if(pl.team != this_team)
961 if(this_team == NUM_SPECTATOR)
963 if(autocvar_hud_panel_scoreboard_spectators_showping)
964 field = Scoreboard_GetField(pl, SP_PING);
966 else if(autocvar_hud_panel_scoreboard_others_showscore)
967 field = Scoreboard_GetField(pl, SP_SCORE);
969 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
970 float column_width = stringwidth(str, true, hud_fontsize);
971 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
973 if(column_width > max_name_width)
974 max_name_width = column_width;
975 column_width = max_name_width;
979 fieldsize = stringwidth(field, false, hud_fontsize);
980 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
983 if(pos.x + column_width > width_limit)
988 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
993 pos.x = item_pos.x + hud_fontsize.x * 0.5;
994 pos.y += hud_fontsize.y * 1.25;
998 vector name_pos = pos;
999 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1000 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1001 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1004 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1005 h_size.y = hud_fontsize.y;
1006 vector field_pos = pos;
1007 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1008 field_pos.x += column_width - h_size.x;
1010 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1011 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1012 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1014 pos.x += column_width;
1015 pos.x += hud_fontsize.x;
1017 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1020 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1022 int max_players = 999;
1023 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1025 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1028 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1029 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1030 height /= team_count;
1033 height -= panel_bg_padding * 2; // - padding
1034 max_players = floor(height / (hud_fontsize.y * 1.25));
1035 if(max_players <= 1)
1037 if(max_players == tm.team_size)
1042 entity me = playerslots[current_player];
1044 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1045 panel_size.y += panel_bg_padding * 2;
1048 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1049 if(panel.current_panel_bg != "0")
1050 end_pos.y += panel_bg_border * 2;
1052 if(panel_bg_padding)
1054 panel_pos += '1 1 0' * panel_bg_padding;
1055 panel_size -= '2 2 0' * panel_bg_padding;
1059 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1063 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1065 pos.y += 1.25 * hud_fontsize.y;
1068 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1070 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1073 // print header row and highlight columns
1074 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1076 // fill the table and draw the rows
1077 bool is_self = false;
1078 bool self_shown = false;
1080 for(pl = players.sort_next; pl; pl = pl.sort_next)
1082 if(pl.team != tm.team)
1084 if(i == max_players - 2 && pl != me)
1086 if(!self_shown && me.team == tm.team)
1088 Scoreboard_DrawItem(pos, rgb, me, true, i);
1090 pos.y += 1.25 * hud_fontsize.y;
1094 if(i >= max_players - 1)
1096 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1099 is_self = (pl.sv_entnum == current_player);
1100 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1103 pos.y += 1.25 * hud_fontsize.y;
1107 panel_size.x += panel_bg_padding * 2; // restore initial width
1111 bool Scoreboard_WouldDraw()
1113 if (MUTATOR_CALLHOOK(DrawScoreboard))
1115 else if (QuickMenu_IsOpened())
1117 else if (HUD_Radar_Clickable())
1119 else if (scoreboard_showscores)
1121 else if (intermission == 1)
1123 else if (intermission == 2)
1125 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1127 else if (scoreboard_showscores_force)
1132 float average_accuracy;
1133 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1135 WepSet weapons_stat = WepSet_GetFromStat();
1136 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1137 int disownedcnt = 0;
1139 FOREACH(Weapons, it != WEP_Null, {
1140 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1142 WepSet set = it.m_wepset;
1143 if(it.spawnflags & WEP_TYPE_OTHER)
1148 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1150 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1157 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1158 if (weapon_cnt <= 0) return pos;
1161 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1163 int columnns = ceil(weapon_cnt / rows);
1165 float weapon_height = 29;
1166 float height = hud_fontsize.y + weapon_height;
1168 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1169 pos.y += 1.25 * hud_fontsize.y;
1170 if(panel.current_panel_bg != "0")
1171 pos.y += panel_bg_border;
1174 panel_size.y = height * rows;
1175 panel_size.y += panel_bg_padding * 2;
1178 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1179 if(panel.current_panel_bg != "0")
1180 end_pos.y += panel_bg_border * 2;
1182 if(panel_bg_padding)
1184 panel_pos += '1 1 0' * panel_bg_padding;
1185 panel_size -= '2 2 0' * panel_bg_padding;
1189 vector tmp = panel_size;
1191 float weapon_width = tmp.x / columnns / rows;
1194 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1198 // column highlighting
1199 for (int i = 0; i < columnns; ++i)
1201 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1204 for (int i = 0; i < rows; ++i)
1205 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1208 average_accuracy = 0;
1209 int weapons_with_stats = 0;
1211 pos.x += weapon_width / 2;
1213 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1216 Accuracy_LoadColors();
1218 float oldposx = pos.x;
1222 FOREACH(Weapons, it != WEP_Null, {
1223 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1225 WepSet set = it.m_wepset;
1226 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1228 if (it.spawnflags & WEP_TYPE_OTHER)
1232 if (weapon_stats >= 0)
1233 weapon_alpha = sbt_fg_alpha;
1235 weapon_alpha = 0.2 * sbt_fg_alpha;
1238 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1240 if (weapon_stats >= 0) {
1241 weapons_with_stats += 1;
1242 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1245 s = sprintf("%d%%", weapon_stats * 100);
1248 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1250 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1251 rgb = Accuracy_GetColor(weapon_stats);
1253 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1255 tmpos.x += weapon_width * rows;
1256 pos.x += weapon_width * rows;
1257 if (rows == 2 && column == columnns - 1) {
1265 if (weapons_with_stats)
1266 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1268 panel_size.x += panel_bg_padding * 2; // restore initial width
1272 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1274 pos.x += hud_fontsize.x * 0.25;
1275 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1276 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1277 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1279 pos.y += hud_fontsize.y;
1284 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1285 float stat_secrets_found, stat_secrets_total;
1286 float stat_monsters_killed, stat_monsters_total;
1290 // get monster stats
1291 stat_monsters_killed = STAT(MONSTERS_KILLED);
1292 stat_monsters_total = STAT(MONSTERS_TOTAL);
1294 // get secrets stats
1295 stat_secrets_found = STAT(SECRETS_FOUND);
1296 stat_secrets_total = STAT(SECRETS_TOTAL);
1298 // get number of rows
1299 if(stat_secrets_total)
1301 if(stat_monsters_total)
1304 // if no rows, return
1308 // draw table header
1309 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1310 pos.y += 1.25 * hud_fontsize.y;
1311 if(panel.current_panel_bg != "0")
1312 pos.y += panel_bg_border;
1315 panel_size.y = hud_fontsize.y * rows;
1316 panel_size.y += panel_bg_padding * 2;
1319 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1320 if(panel.current_panel_bg != "0")
1321 end_pos.y += panel_bg_border * 2;
1323 if(panel_bg_padding)
1325 panel_pos += '1 1 0' * panel_bg_padding;
1326 panel_size -= '2 2 0' * panel_bg_padding;
1330 vector tmp = panel_size;
1333 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1336 if(stat_monsters_total)
1338 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1339 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1343 if(stat_secrets_total)
1345 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1346 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1349 panel_size.x += panel_bg_padding * 2; // restore initial width
1354 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1357 RANKINGS_RECEIVED_CNT = 0;
1358 for (i=RANKINGS_CNT-1; i>=0; --i)
1360 ++RANKINGS_RECEIVED_CNT;
1362 if (RANKINGS_RECEIVED_CNT == 0)
1365 vector hl_rgb = rgb + '0.5 0.5 0.5';
1367 pos.y += hud_fontsize.y;
1368 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1369 pos.y += 1.25 * hud_fontsize.y;
1370 if(panel.current_panel_bg != "0")
1371 pos.y += panel_bg_border;
1376 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1378 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1383 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1385 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1389 float ranksize = 3 * hud_fontsize.x;
1390 float timesize = 5 * hud_fontsize.x;
1391 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1392 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1393 columns = min(columns, RANKINGS_RECEIVED_CNT);
1395 // expand name column to fill the entire row
1396 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1397 namesize += available_space;
1398 columnsize.x += available_space;
1400 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1401 panel_size.y += panel_bg_padding * 2;
1405 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1406 if(panel.current_panel_bg != "0")
1407 end_pos.y += panel_bg_border * 2;
1409 if(panel_bg_padding)
1411 panel_pos += '1 1 0' * panel_bg_padding;
1412 panel_size -= '2 2 0' * panel_bg_padding;
1418 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1420 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1422 int column = 0, j = 0;
1423 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1430 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1431 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1432 else if(!((j + column) & 1) && sbt_highlight)
1433 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1435 str = count_ordinal(i+1);
1436 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1437 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1438 str = ColorTranslateRGB(grecordholder[i]);
1440 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1441 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1443 pos.y += 1.25 * hud_fontsize.y;
1445 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1449 pos.x += panel_size.x / columns;
1450 pos.y = panel_pos.y;
1454 panel_size.x += panel_bg_padding * 2; // restore initial width
1458 void Scoreboard_Draw()
1460 if(!autocvar__hud_configure)
1462 if(!hud_draw_maximized) return;
1464 // frametime checks allow to toggle the scoreboard even when the game is paused
1465 if(scoreboard_active) {
1466 if(hud_configure_menu_open == 1)
1467 scoreboard_fade_alpha = 1;
1468 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1469 if (scoreboard_fadeinspeed && frametime)
1470 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1472 scoreboard_fade_alpha = 1;
1473 if(hud_fontsize_str != autocvar_hud_fontsize)
1475 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1476 Scoreboard_initFieldSizes();
1477 if(hud_fontsize_str)
1478 strunzone(hud_fontsize_str);
1479 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1483 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1484 if (scoreboard_fadeoutspeed && frametime)
1485 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1487 scoreboard_fade_alpha = 0;
1490 if (!scoreboard_fade_alpha)
1494 scoreboard_fade_alpha = 0;
1496 if (autocvar_hud_panel_scoreboard_dynamichud)
1499 HUD_Scale_Disable();
1501 if(scoreboard_fade_alpha <= 0)
1503 panel_fade_alpha *= scoreboard_fade_alpha;
1504 HUD_Panel_LoadCvars();
1506 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1507 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1508 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1509 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1510 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1511 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1513 // don't overlap with con_notify
1514 if(!autocvar__hud_configure)
1515 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1517 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1518 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1519 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1520 panel_size.x = fixed_scoreboard_width;
1522 Scoreboard_UpdatePlayerTeams();
1524 vector pos = panel_pos;
1530 vector sb_heading_fontsize;
1531 sb_heading_fontsize = hud_fontsize * 2;
1532 draw_beginBoldFont();
1533 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1536 pos.y += sb_heading_fontsize.y;
1537 if(panel.current_panel_bg != "0")
1538 pos.y += panel_bg_border;
1540 // Draw the scoreboard
1541 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1544 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1548 vector panel_bg_color_save = panel_bg_color;
1549 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1550 if(panel.current_panel_bg != "0")
1551 team_score_baseoffset.x -= panel_bg_border;
1552 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1554 if(tm.team == NUM_SPECTATOR)
1559 draw_beginBoldFont();
1560 vector rgb = Team_ColorRGB(tm.team);
1561 str = ftos(tm.(teamscores(ts_primary)));
1562 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1563 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1565 if(ts_primary != ts_secondary)
1567 str = ftos(tm.(teamscores(ts_secondary)));
1568 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1569 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1572 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1573 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1574 else if(panel_bg_color_team > 0)
1575 panel_bg_color = rgb * panel_bg_color_team;
1577 panel_bg_color = rgb;
1578 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1580 panel_bg_color = panel_bg_color_save;
1584 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1585 if(tm.team != NUM_SPECTATOR)
1587 // display it anyway
1588 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1591 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1593 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1594 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1596 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1597 if(race_speedaward) {
1598 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);
1599 pos.y += 1.25 * hud_fontsize.y;
1601 if(race_speedaward_alltimebest) {
1602 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);
1603 pos.y += 1.25 * hud_fontsize.y;
1605 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1608 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1611 for(pl = players.sort_next; pl; pl = pl.sort_next)
1613 if(pl.team == NUM_SPECTATOR)
1615 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1616 if(tm.team == NUM_SPECTATOR)
1618 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1619 draw_beginBoldFont();
1620 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1622 pos.y += 1.25 * hud_fontsize.y;
1624 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1625 pos.y += 1.25 * hud_fontsize.y;
1631 // Print info string
1633 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1634 tl = STAT(TIMELIMIT);
1635 fl = STAT(FRAGLIMIT);
1636 ll = STAT(LEADLIMIT);
1637 if(gametype == MAPINFO_TYPE_LMS)
1640 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1645 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1649 str = strcat(str, _(" or"));
1652 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1653 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1654 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1655 TranslateScoresLabel(teamscores_label(ts_primary))));
1659 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1660 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1661 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1662 TranslateScoresLabel(scores_label(ps_primary))));
1667 if(tl > 0 || fl > 0)
1668 str = strcat(str, _(" or"));
1671 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1672 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1673 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1674 TranslateScoresLabel(teamscores_label(ts_primary))));
1678 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1679 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1680 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1681 TranslateScoresLabel(scores_label(ps_primary))));
1686 pos.y += 1.2 * hud_fontsize.y;
1687 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1689 // print information about respawn status
1690 float respawn_time = STAT(RESPAWN_TIME);
1694 if(respawn_time < 0)
1696 // a negative number means we are awaiting respawn, time value is still the same
1697 respawn_time *= -1; // remove mark now that we checked it
1699 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1700 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1702 str = sprintf(_("^1Respawning in ^3%s^1..."),
1703 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1704 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1706 count_seconds(ceil(respawn_time - time))
1710 else if(time < respawn_time)
1712 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1713 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1714 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1716 count_seconds(ceil(respawn_time - time))
1720 else if(time >= respawn_time)
1721 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1723 pos.y += 1.2 * hud_fontsize.y;
1724 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1727 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;