1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/net_linked.qh>
7 #include <common/mapinfo.qh>
8 #include <common/minigames/cl_minigames.qh>
9 #include <common/scores.qh>
10 #include <common/stats.qh>
11 #include <common/teams.qh>
15 const int MAX_SBT_FIELDS = MAX_SCORE;
17 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
18 float sbt_field_size[MAX_SBT_FIELDS + 1];
19 string sbt_field_title[MAX_SBT_FIELDS + 1];
22 string autocvar_hud_fontsize;
23 string hud_fontsize_str;
28 float sbt_fg_alpha_self;
30 float sbt_highlight_alpha;
31 float sbt_highlight_alpha_self;
33 // provide basic panel cvars to old clients
34 // TODO remove them after a future release (0.8.2+)
35 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
36 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
37 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
38 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
39 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
40 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
41 noref string autocvar_hud_panel_scoreboard_bg_border = "";
42 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
44 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
45 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
46 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
47 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
48 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
49 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
50 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
51 bool autocvar_hud_panel_scoreboard_table_highlight = true;
52 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
53 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
54 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
55 float autocvar_hud_panel_scoreboard_namesize = 15;
57 bool autocvar_hud_panel_scoreboard_accuracy = true;
58 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
59 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
60 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
62 bool autocvar_hud_panel_scoreboard_dynamichud = false;
64 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
65 bool autocvar_hud_panel_scoreboard_others_showscore = true;
66 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
67 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
68 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
71 void drawstringright(vector, string, vector, vector, float, float);
72 void drawstringcenter(vector, string, vector, vector, float, float);
74 // wrapper to put all possible scores titles through gettext
75 string TranslateScoresLabel(string l)
79 case "bckills": return CTX(_("SCO^bckills"));
80 case "bctime": return CTX(_("SCO^bctime"));
81 case "caps": return CTX(_("SCO^caps"));
82 case "captime": return CTX(_("SCO^captime"));
83 case "deaths": return CTX(_("SCO^deaths"));
84 case "destroyed": return CTX(_("SCO^destroyed"));
85 case "dmg": return CTX(_("SCO^damage"));
86 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
87 case "drops": return CTX(_("SCO^drops"));
88 case "faults": return CTX(_("SCO^faults"));
89 case "fckills": return CTX(_("SCO^fckills"));
90 case "goals": return CTX(_("SCO^goals"));
91 case "kckills": return CTX(_("SCO^kckills"));
92 case "kdratio": return CTX(_("SCO^kdratio"));
93 case "kd": return CTX(_("SCO^k/d"));
94 case "kdr": return CTX(_("SCO^kdr"));
95 case "kills": return CTX(_("SCO^kills"));
96 case "laps": return CTX(_("SCO^laps"));
97 case "lives": return CTX(_("SCO^lives"));
98 case "losses": return CTX(_("SCO^losses"));
99 case "name": return CTX(_("SCO^name"));
100 case "sum": return CTX(_("SCO^sum"));
101 case "nick": return CTX(_("SCO^nick"));
102 case "objectives": return CTX(_("SCO^objectives"));
103 case "pickups": return CTX(_("SCO^pickups"));
104 case "ping": return CTX(_("SCO^ping"));
105 case "pl": return CTX(_("SCO^pl"));
106 case "pushes": return CTX(_("SCO^pushes"));
107 case "rank": return CTX(_("SCO^rank"));
108 case "returns": return CTX(_("SCO^returns"));
109 case "revivals": return CTX(_("SCO^revivals"));
110 case "rounds": return CTX(_("SCO^rounds won"));
111 case "score": return CTX(_("SCO^score"));
112 case "suicides": return CTX(_("SCO^suicides"));
113 case "takes": return CTX(_("SCO^takes"));
114 case "ticks": return CTX(_("SCO^ticks"));
119 void Scoreboard_InitScores()
123 ps_primary = ps_secondary = NULL;
124 ts_primary = ts_secondary = -1;
125 FOREACH(Scores, true, {
126 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
127 if(f == SFL_SORT_PRIO_PRIMARY)
129 if(f == SFL_SORT_PRIO_SECONDARY)
132 if(ps_secondary == NULL)
133 ps_secondary = ps_primary;
135 for(i = 0; i < MAX_TEAMSCORE; ++i)
137 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
138 if(f == SFL_SORT_PRIO_PRIMARY)
140 if(f == SFL_SORT_PRIO_SECONDARY)
143 if(ts_secondary == -1)
144 ts_secondary = ts_primary;
146 Cmd_Scoreboard_SetFields(0);
149 float SetTeam(entity pl, float Team);
151 void Scoreboard_UpdatePlayerTeams()
156 for(pl = players.sort_next; pl; pl = pl.sort_next)
159 Team = entcs_GetScoreTeam(pl.sv_entnum);
160 if(SetTeam(pl, Team))
163 Scoreboard_UpdatePlayerPos(pl);
167 pl = players.sort_next;
172 print(strcat("PNUM: ", ftos(num), "\n"));
177 int Scoreboard_CompareScore(int vl, int vr, int f)
179 TC(int, vl); TC(int, vr); TC(int, f);
180 if(f & SFL_ZERO_IS_WORST)
182 if(vl == 0 && vr != 0)
184 if(vl != 0 && vr == 0)
188 return IS_INCREASING(f);
190 return IS_DECREASING(f);
194 float Scoreboard_ComparePlayerScores(entity left, entity right)
197 vl = entcs_GetTeam(left.sv_entnum);
198 vr = entcs_GetTeam(right.sv_entnum);
210 if(vl == NUM_SPECTATOR)
212 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
214 if(!left.gotscores && right.gotscores)
219 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
223 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
227 FOREACH(Scores, true, {
228 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
229 if (r >= 0) return r;
232 if (left.sv_entnum < right.sv_entnum)
238 void Scoreboard_UpdatePlayerPos(entity player)
241 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
243 SORT_SWAP(player, ent);
245 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
247 SORT_SWAP(ent, player);
251 float Scoreboard_CompareTeamScores(entity left, entity right)
255 if(left.team == NUM_SPECTATOR)
257 if(right.team == NUM_SPECTATOR)
260 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
264 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
268 for(i = 0; i < MAX_TEAMSCORE; ++i)
270 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
275 if (left.team < right.team)
281 void Scoreboard_UpdateTeamPos(entity Team)
284 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
286 SORT_SWAP(Team, ent);
288 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
290 SORT_SWAP(ent, Team);
294 void Cmd_Scoreboard_Help()
296 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
297 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
298 LOG_INFO(_("Usage:\n"));
299 LOG_INFO(_("^2scoreboard_columns_set default\n"));
300 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
301 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
302 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
305 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
306 LOG_INFO(_("^3ping^7 Ping time\n"));
307 LOG_INFO(_("^3pl^7 Packet loss\n"));
308 LOG_INFO(_("^3elo^7 Player ELO\n"));
309 LOG_INFO(_("^3kills^7 Number of kills\n"));
310 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
311 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
312 LOG_INFO(_("^3frags^7 kills - suicides\n"));
313 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
314 LOG_INFO(_("^3dmg^7 The total damage done\n"));
315 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
316 LOG_INFO(_("^3sum^7 frags - deaths\n"));
317 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
318 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
319 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
320 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
321 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
322 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
323 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
324 LOG_INFO(_("^3rank^7 Player rank\n"));
325 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
326 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
327 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
328 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
329 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
330 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
331 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
332 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
333 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
334 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
335 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
336 LOG_INFO(_("^3score^7 Total score\n"));
339 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
340 "of game types, then a slash, to make the field show up only in these\n"
341 "or in all but these game types. You can also specify 'all' as a\n"
342 "field to show all fields available for the current game mode.\n\n"));
344 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
345 "include/exclude ALL teams/noteams game modes.\n\n"));
347 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
348 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
349 "right of the vertical bar aligned to the right.\n"));
350 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
351 "other gamemodes except DM.\n"));
354 // NOTE: adding a gametype with ? to not warn for an optional field
355 // make sure it's excluded in a previous exclusive rule, if any
356 // otherwise the previous exclusive rule warns anyway
357 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
358 #define SCOREBOARD_DEFAULT_COLUMNS \
360 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
361 " -teams,lms/deaths +ft,tdm/deaths" \
362 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
363 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
364 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
365 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
366 " +lms/lives +lms/rank" \
367 " +kh/caps +kh/pushes +kh/destroyed" \
368 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
369 " +as/objectives +nb/faults +nb/goals" \
370 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
371 " -lms,rc,cts,inv,nb/score"
373 void Cmd_Scoreboard_SetFields(int argc)
378 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
382 return; // do nothing, we don't know gametype and scores yet
384 // sbt_fields uses strunzone on the titles!
385 if(!sbt_field_title[0])
386 for(i = 0; i < MAX_SBT_FIELDS; ++i)
387 sbt_field_title[i] = strzone("(null)");
389 // TODO: re enable with gametype dependant cvars?
390 if(argc < 3) // no arguments provided
391 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
394 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
398 if(argv(2) == "default")
399 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
400 else if(argv(2) == "all")
403 s = "ping pl name |";
404 FOREACH(Scores, true, {
406 if(it != ps_secondary)
407 if(scores_label(it) != "")
408 s = strcat(s, " ", scores_label(it));
410 if(ps_secondary != ps_primary)
411 s = strcat(s, " ", scores_label(ps_secondary));
412 s = strcat(s, " ", scores_label(ps_primary));
413 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
420 hud_fontsize = HUD_GetFontsize("hud_fontsize");
422 for(i = 1; i < argc - 1; ++i)
428 if(substring(str, 0, 1) == "?")
431 str = substring(str, 1, strlen(str) - 1);
434 slash = strstrofs(str, "/", 0);
437 pattern = substring(str, 0, slash);
438 str = substring(str, slash + 1, strlen(str) - (slash + 1));
440 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
444 strunzone(sbt_field_title[sbt_num_fields]);
445 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
446 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
447 str = strtolower(str);
452 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
453 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
454 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
455 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
456 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
457 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
458 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
459 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
460 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
463 FOREACH(Scores, true, {
464 if (str == strtolower(scores_label(it))) {
466 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
476 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
480 sbt_field[sbt_num_fields] = j;
483 if(j == ps_secondary)
484 have_secondary = true;
489 if(sbt_num_fields >= MAX_SBT_FIELDS)
493 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
495 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
496 have_secondary = true;
497 if(ps_primary == ps_secondary)
498 have_secondary = true;
499 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
501 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
505 strunzone(sbt_field_title[sbt_num_fields]);
506 for(i = sbt_num_fields; i > 0; --i)
508 sbt_field_title[i] = sbt_field_title[i-1];
509 sbt_field_size[i] = sbt_field_size[i-1];
510 sbt_field[i] = sbt_field[i-1];
512 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
513 sbt_field[0] = SP_NAME;
515 LOG_INFO("fixed missing field 'name'\n");
519 strunzone(sbt_field_title[sbt_num_fields]);
520 for(i = sbt_num_fields; i > 1; --i)
522 sbt_field_title[i] = sbt_field_title[i-1];
523 sbt_field_size[i] = sbt_field_size[i-1];
524 sbt_field[i] = sbt_field[i-1];
526 sbt_field_title[1] = strzone("|");
527 sbt_field[1] = SP_SEPARATOR;
528 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
530 LOG_INFO("fixed missing field '|'\n");
533 else if(!have_separator)
535 strunzone(sbt_field_title[sbt_num_fields]);
536 sbt_field_title[sbt_num_fields] = strzone("|");
537 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
538 sbt_field[sbt_num_fields] = SP_SEPARATOR;
540 LOG_INFO("fixed missing field '|'\n");
544 strunzone(sbt_field_title[sbt_num_fields]);
545 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
546 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
547 sbt_field[sbt_num_fields] = ps_secondary;
549 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
553 strunzone(sbt_field_title[sbt_num_fields]);
554 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
555 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
556 sbt_field[sbt_num_fields] = ps_primary;
558 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
562 sbt_field[sbt_num_fields] = SP_END;
566 vector sbt_field_rgb;
567 string sbt_field_icon0;
568 string sbt_field_icon1;
569 string sbt_field_icon2;
570 vector sbt_field_icon0_rgb;
571 vector sbt_field_icon1_rgb;
572 vector sbt_field_icon2_rgb;
573 string Scoreboard_GetName(entity pl)
575 if(ready_waiting && pl.ready)
577 sbt_field_icon0 = "gfx/scoreboard/player_ready";
581 int f = entcs_GetClientColors(pl.sv_entnum);
583 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
584 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
585 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
586 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
587 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
590 return entcs_GetName(pl.sv_entnum);
592 string Scoreboard_GetField(entity pl, PlayerScoreField field)
594 float tmp, num, denom;
597 sbt_field_rgb = '1 1 1';
598 sbt_field_icon0 = "";
599 sbt_field_icon1 = "";
600 sbt_field_icon2 = "";
601 sbt_field_icon0_rgb = '1 1 1';
602 sbt_field_icon1_rgb = '1 1 1';
603 sbt_field_icon2_rgb = '1 1 1';
608 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
609 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
613 tmp = max(0, min(220, f-80)) / 220;
614 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
620 f = pl.ping_packetloss;
621 tmp = pl.ping_movementloss;
622 if(f == 0 && tmp == 0)
624 str = ftos(ceil(f * 100));
626 str = strcat(str, "~", ftos(ceil(tmp * 100)));
627 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
628 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
632 return Scoreboard_GetName(pl);
635 f = pl.(scores(SP_KILLS));
636 f -= pl.(scores(SP_SUICIDES));
640 num = pl.(scores(SP_KILLS));
641 denom = pl.(scores(SP_DEATHS));
644 sbt_field_rgb = '0 1 0';
645 str = sprintf("%d", num);
646 } else if(num <= 0) {
647 sbt_field_rgb = '1 0 0';
648 str = sprintf("%.1f", num/denom);
650 str = sprintf("%.1f", num/denom);
654 f = pl.(scores(SP_KILLS));
655 f -= pl.(scores(SP_DEATHS));
658 sbt_field_rgb = '0 1 0';
660 sbt_field_rgb = '1 1 1';
662 sbt_field_rgb = '1 0 0';
668 float elo = pl.(scores(SP_ELO));
670 case -1: return "...";
671 case -2: return _("N/A");
672 default: return ftos(elo);
676 case SP_DMG: case SP_DMGTAKEN:
677 return sprintf("%.1f k", pl.(scores(field)) / 1000);
679 default: case SP_SCORE:
680 tmp = pl.(scores(field));
681 f = scores_flags(field);
682 if(field == ps_primary)
683 sbt_field_rgb = '1 1 0';
684 else if(field == ps_secondary)
685 sbt_field_rgb = '0 1 1';
687 sbt_field_rgb = '1 1 1';
688 return ScoreString(f, tmp);
693 float sbt_fixcolumnwidth_len;
694 float sbt_fixcolumnwidth_iconlen;
695 float sbt_fixcolumnwidth_marginlen;
697 string Scoreboard_FixColumnWidth(int i, string str)
703 sbt_fixcolumnwidth_iconlen = 0;
705 if(sbt_field_icon0 != "")
707 sz = draw_getimagesize(sbt_field_icon0);
709 if(sbt_fixcolumnwidth_iconlen < f)
710 sbt_fixcolumnwidth_iconlen = f;
713 if(sbt_field_icon1 != "")
715 sz = draw_getimagesize(sbt_field_icon1);
717 if(sbt_fixcolumnwidth_iconlen < f)
718 sbt_fixcolumnwidth_iconlen = f;
721 if(sbt_field_icon2 != "")
723 sz = draw_getimagesize(sbt_field_icon2);
725 if(sbt_fixcolumnwidth_iconlen < f)
726 sbt_fixcolumnwidth_iconlen = f;
729 if(sbt_fixcolumnwidth_iconlen != 0)
731 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
732 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
735 sbt_fixcolumnwidth_marginlen = 0;
737 if(sbt_field[i] == SP_NAME) // name gets all remaining space
740 float remaining_space = 0;
741 for(j = 0; j < sbt_num_fields; ++j)
743 if (sbt_field[i] != SP_SEPARATOR)
744 remaining_space += sbt_field_size[j] + hud_fontsize.x;
745 sbt_field_size[i] = panel_size.x - remaining_space;
747 if (sbt_fixcolumnwidth_iconlen != 0)
748 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
749 float namesize = panel_size.x - remaining_space;
750 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
751 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
753 max_namesize = vid_conwidth - remaining_space;
756 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
758 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
759 if(sbt_field_size[i] < f)
760 sbt_field_size[i] = f;
765 void Scoreboard_initFieldSizes()
767 for(int i = 0; i < sbt_num_fields; ++i)
769 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
770 Scoreboard_FixColumnWidth(i, "");
774 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
777 vector column_dim = eY * panel_size.y;
779 column_dim.y -= 1.25 * hud_fontsize.y;
780 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
781 pos.x += hud_fontsize.x * 0.5;
782 for(i = 0; i < sbt_num_fields; ++i)
784 if(sbt_field[i] == SP_SEPARATOR)
786 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
789 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
790 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
791 pos.x += column_dim.x;
793 if(sbt_field[i] == SP_SEPARATOR)
795 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
796 for(i = sbt_num_fields - 1; i > 0; --i)
798 if(sbt_field[i] == SP_SEPARATOR)
801 pos.x -= sbt_field_size[i];
806 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
807 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
810 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
811 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
812 pos.x -= hud_fontsize.x;
817 pos.y += 1.25 * hud_fontsize.y;
821 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
823 TC(bool, is_self); TC(int, pl_number);
825 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
827 vector h_pos = item_pos;
828 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
829 // alternated rows highlighting
831 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
832 else if((sbt_highlight) && (!(pl_number % 2)))
833 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
835 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
837 vector pos = item_pos;
838 pos.x += hud_fontsize.x * 0.5;
839 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
840 vector tmp = '0 0 0';
842 PlayerScoreField field;
843 for(i = 0; i < sbt_num_fields; ++i)
845 field = sbt_field[i];
846 if(field == SP_SEPARATOR)
849 if(is_spec && field != SP_NAME && field != SP_PING) {
850 pos.x += sbt_field_size[i] + hud_fontsize.x;
853 str = Scoreboard_GetField(pl, field);
854 str = Scoreboard_FixColumnWidth(i, str);
856 pos.x += sbt_field_size[i] + hud_fontsize.x;
858 if(field == SP_NAME) {
859 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
860 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
862 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
863 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
866 tmp.x = sbt_field_size[i] + hud_fontsize.x;
867 if(sbt_field_icon0 != "")
868 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
869 if(sbt_field_icon1 != "")
870 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
871 if(sbt_field_icon2 != "")
872 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
875 if(sbt_field[i] == SP_SEPARATOR)
877 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
878 for(i = sbt_num_fields-1; i > 0; --i)
880 field = sbt_field[i];
881 if(field == SP_SEPARATOR)
884 if(is_spec && field != SP_NAME && field != SP_PING) {
885 pos.x -= sbt_field_size[i] + hud_fontsize.x;
889 str = Scoreboard_GetField(pl, field);
890 str = Scoreboard_FixColumnWidth(i, str);
892 if(field == SP_NAME) {
893 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
894 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
896 tmp.x = sbt_fixcolumnwidth_len;
897 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
900 tmp.x = sbt_field_size[i];
901 if(sbt_field_icon0 != "")
902 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
903 if(sbt_field_icon1 != "")
904 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
905 if(sbt_field_icon2 != "")
906 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
907 pos.x -= sbt_field_size[i] + hud_fontsize.x;
912 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
915 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
918 vector h_pos = item_pos;
919 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
921 bool complete = (this_team == NUM_SPECTATOR);
924 if((sbt_highlight) && (!(pl_number % 2)))
925 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
927 vector pos = item_pos;
928 pos.x += hud_fontsize.x * 0.5;
929 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
931 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
933 width_limit -= stringwidth("...", false, hud_fontsize);
934 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
935 static float max_name_width = 0;
938 float min_fieldsize = 0;
939 float fieldpadding = hud_fontsize.x * 0.25;
940 if(this_team == NUM_SPECTATOR)
942 if(autocvar_hud_panel_scoreboard_spectators_showping)
943 min_fieldsize = stringwidth("999", false, hud_fontsize);
945 else if(autocvar_hud_panel_scoreboard_others_showscore)
946 min_fieldsize = stringwidth("99", false, hud_fontsize);
947 for(i = 0; pl; pl = pl.sort_next)
949 if(pl.team != this_team)
955 if(this_team == NUM_SPECTATOR)
957 if(autocvar_hud_panel_scoreboard_spectators_showping)
958 field = Scoreboard_GetField(pl, SP_PING);
960 else if(autocvar_hud_panel_scoreboard_others_showscore)
961 field = Scoreboard_GetField(pl, SP_SCORE);
963 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
964 float column_width = stringwidth(str, true, hud_fontsize);
965 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
967 if(column_width > max_name_width)
968 max_name_width = column_width;
969 column_width = max_name_width;
973 fieldsize = stringwidth(field, false, hud_fontsize);
974 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
977 if(pos.x + column_width > width_limit)
982 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
987 pos.x = item_pos.x + hud_fontsize.x * 0.5;
988 pos.y += hud_fontsize.y * 1.25;
992 vector name_pos = pos;
993 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
994 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
995 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
998 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
999 h_size.y = hud_fontsize.y;
1000 vector field_pos = pos;
1001 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1002 field_pos.x += column_width - h_size.x;
1004 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1005 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1006 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1008 pos.x += column_width;
1009 pos.x += hud_fontsize.x;
1011 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1014 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1016 int max_players = 999;
1017 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1019 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1022 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1023 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1024 height /= team_count;
1027 height -= panel_bg_padding * 2; // - padding
1028 max_players = floor(height / (hud_fontsize.y * 1.25));
1029 if(max_players <= 1)
1031 if(max_players == tm.team_size)
1036 entity me = playerslots[current_player];
1038 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1039 panel_size.y += panel_bg_padding * 2;
1042 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1043 if(panel.current_panel_bg != "0")
1044 end_pos.y += panel_bg_border * 2;
1046 if(panel_bg_padding)
1048 panel_pos += '1 1 0' * panel_bg_padding;
1049 panel_size -= '2 2 0' * panel_bg_padding;
1053 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1057 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1059 pos.y += 1.25 * hud_fontsize.y;
1062 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1064 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1067 // print header row and highlight columns
1068 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1070 // fill the table and draw the rows
1071 bool is_self = false;
1072 bool self_shown = false;
1074 for(pl = players.sort_next; pl; pl = pl.sort_next)
1076 if(pl.team != tm.team)
1078 if(i == max_players - 2 && pl != me)
1080 if(!self_shown && me.team == tm.team)
1082 Scoreboard_DrawItem(pos, rgb, me, true, i);
1084 pos.y += 1.25 * hud_fontsize.y;
1088 if(i >= max_players - 1)
1090 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1093 is_self = (pl.sv_entnum == current_player);
1094 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1097 pos.y += 1.25 * hud_fontsize.y;
1101 panel_size.x += panel_bg_padding * 2; // restore initial width
1105 bool Scoreboard_WouldDraw()
1107 if (MUTATOR_CALLHOOK(DrawScoreboard))
1109 else if (QuickMenu_IsOpened())
1111 else if (HUD_Radar_Clickable())
1113 else if (scoreboard_showscores)
1115 else if (intermission == 1)
1117 else if (intermission == 2)
1119 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1121 else if (scoreboard_showscores_force)
1126 float average_accuracy;
1127 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1129 WepSet weapons_stat = WepSet_GetFromStat();
1130 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1131 int disownedcnt = 0;
1133 FOREACH(Weapons, it != WEP_Null, {
1134 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1136 WepSet set = it.m_wepset;
1137 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1139 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1146 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1147 if (weapon_cnt <= 0) return pos;
1150 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1152 int columnns = ceil(weapon_cnt / rows);
1154 float weapon_height = 29;
1155 float height = hud_fontsize.y + weapon_height;
1157 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1158 pos.y += 1.25 * hud_fontsize.y;
1159 if(panel.current_panel_bg != "0")
1160 pos.y += panel_bg_border;
1163 panel_size.y = height * rows;
1164 panel_size.y += panel_bg_padding * 2;
1167 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1168 if(panel.current_panel_bg != "0")
1169 end_pos.y += panel_bg_border * 2;
1171 if(panel_bg_padding)
1173 panel_pos += '1 1 0' * panel_bg_padding;
1174 panel_size -= '2 2 0' * panel_bg_padding;
1178 vector tmp = panel_size;
1180 float weapon_width = tmp.x / columnns / rows;
1183 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1187 // column highlighting
1188 for (int i = 0; i < columnns; ++i)
1190 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1193 for (int i = 0; i < rows; ++i)
1194 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1197 average_accuracy = 0;
1198 int weapons_with_stats = 0;
1200 pos.x += weapon_width / 2;
1202 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1205 Accuracy_LoadColors();
1207 float oldposx = pos.x;
1211 FOREACH(Weapons, it != WEP_Null, {
1212 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1214 WepSet set = it.m_wepset;
1215 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1219 if (weapon_stats >= 0)
1220 weapon_alpha = sbt_fg_alpha;
1222 weapon_alpha = 0.2 * sbt_fg_alpha;
1225 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1227 if (weapon_stats >= 0) {
1228 weapons_with_stats += 1;
1229 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1232 s = sprintf("%d%%", weapon_stats * 100);
1235 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1237 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1238 rgb = Accuracy_GetColor(weapon_stats);
1240 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1242 tmpos.x += weapon_width * rows;
1243 pos.x += weapon_width * rows;
1244 if (rows == 2 && column == columnns - 1) {
1252 if (weapons_with_stats)
1253 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1255 panel_size.x += panel_bg_padding * 2; // restore initial width
1259 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1261 pos.x += hud_fontsize.x * 0.25;
1262 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1263 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1264 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1266 pos.y += hud_fontsize.y;
1271 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1272 float stat_secrets_found, stat_secrets_total;
1273 float stat_monsters_killed, stat_monsters_total;
1277 // get monster stats
1278 stat_monsters_killed = STAT(MONSTERS_KILLED);
1279 stat_monsters_total = STAT(MONSTERS_TOTAL);
1281 // get secrets stats
1282 stat_secrets_found = STAT(SECRETS_FOUND);
1283 stat_secrets_total = STAT(SECRETS_TOTAL);
1285 // get number of rows
1286 if(stat_secrets_total)
1288 if(stat_monsters_total)
1291 // if no rows, return
1295 // draw table header
1296 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1297 pos.y += 1.25 * hud_fontsize.y;
1298 if(panel.current_panel_bg != "0")
1299 pos.y += panel_bg_border;
1302 panel_size.y = hud_fontsize.y * rows;
1303 panel_size.y += panel_bg_padding * 2;
1306 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1307 if(panel.current_panel_bg != "0")
1308 end_pos.y += panel_bg_border * 2;
1310 if(panel_bg_padding)
1312 panel_pos += '1 1 0' * panel_bg_padding;
1313 panel_size -= '2 2 0' * panel_bg_padding;
1317 vector tmp = panel_size;
1320 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1323 if(stat_monsters_total)
1325 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1326 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1330 if(stat_secrets_total)
1332 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1333 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1336 panel_size.x += panel_bg_padding * 2; // restore initial width
1341 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1344 RANKINGS_RECEIVED_CNT = 0;
1345 for (i=RANKINGS_CNT-1; i>=0; --i)
1347 ++RANKINGS_RECEIVED_CNT;
1349 if (RANKINGS_RECEIVED_CNT == 0)
1352 vector hl_rgb = rgb + '0.5 0.5 0.5';
1354 pos.y += hud_fontsize.y;
1355 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1356 pos.y += 1.25 * hud_fontsize.y;
1357 if(panel.current_panel_bg != "0")
1358 pos.y += panel_bg_border;
1363 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1365 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1370 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1372 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1376 float ranksize = 3 * hud_fontsize.x;
1377 float timesize = 5 * hud_fontsize.x;
1378 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1379 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1380 columns = min(columns, RANKINGS_RECEIVED_CNT);
1382 // expand name column to fill the entire row
1383 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1384 namesize += available_space;
1385 columnsize.x += available_space;
1387 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1388 panel_size.y += panel_bg_padding * 2;
1392 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1393 if(panel.current_panel_bg != "0")
1394 end_pos.y += panel_bg_border * 2;
1396 if(panel_bg_padding)
1398 panel_pos += '1 1 0' * panel_bg_padding;
1399 panel_size -= '2 2 0' * panel_bg_padding;
1405 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1407 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1409 int column = 0, j = 0;
1410 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1417 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1418 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1419 else if(!((j + column) & 1) && sbt_highlight)
1420 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1422 str = count_ordinal(i+1);
1423 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1424 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1425 str = ColorTranslateRGB(grecordholder[i]);
1427 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1428 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1430 pos.y += 1.25 * hud_fontsize.y;
1432 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1436 pos.x += panel_size.x / columns;
1437 pos.y = panel_pos.y;
1441 panel_size.x += panel_bg_padding * 2; // restore initial width
1445 void Scoreboard_Draw()
1447 if(!autocvar__hud_configure)
1449 if(!hud_draw_maximized) return;
1451 // frametime checks allow to toggle the scoreboard even when the game is paused
1452 if(scoreboard_active) {
1453 if(hud_configure_menu_open == 1)
1454 scoreboard_fade_alpha = 1;
1455 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1456 if (scoreboard_fadeinspeed && frametime)
1457 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1459 scoreboard_fade_alpha = 1;
1460 if(hud_fontsize_str != autocvar_hud_fontsize)
1462 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1463 Scoreboard_initFieldSizes();
1464 if(hud_fontsize_str)
1465 strunzone(hud_fontsize_str);
1466 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1470 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1471 if (scoreboard_fadeoutspeed && frametime)
1472 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1474 scoreboard_fade_alpha = 0;
1477 if (!scoreboard_fade_alpha)
1481 scoreboard_fade_alpha = 0;
1483 if (autocvar_hud_panel_scoreboard_dynamichud)
1486 HUD_Scale_Disable();
1488 if(scoreboard_fade_alpha <= 0)
1490 panel_fade_alpha *= scoreboard_fade_alpha;
1491 HUD_Panel_LoadCvars();
1493 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1494 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1495 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1496 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1497 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1498 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1500 // don't overlap with con_notify
1501 if(!autocvar__hud_configure)
1502 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1504 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1505 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1506 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1507 panel_size.x = fixed_scoreboard_width;
1509 Scoreboard_UpdatePlayerTeams();
1511 vector pos = panel_pos;
1517 vector sb_heading_fontsize;
1518 sb_heading_fontsize = hud_fontsize * 2;
1519 draw_beginBoldFont();
1520 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1523 pos.y += sb_heading_fontsize.y;
1524 if(panel.current_panel_bg != "0")
1525 pos.y += panel_bg_border;
1527 // Draw the scoreboard
1528 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1531 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1535 vector panel_bg_color_save = panel_bg_color;
1536 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1537 if(panel.current_panel_bg != "0")
1538 team_score_baseoffset.x -= panel_bg_border;
1539 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1541 if(tm.team == NUM_SPECTATOR)
1546 draw_beginBoldFont();
1547 vector rgb = Team_ColorRGB(tm.team);
1548 str = ftos(tm.(teamscores(ts_primary)));
1549 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1550 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1552 if(ts_primary != ts_secondary)
1554 str = ftos(tm.(teamscores(ts_secondary)));
1555 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1556 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1559 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1560 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1561 else if(panel_bg_color_team > 0)
1562 panel_bg_color = rgb * panel_bg_color_team;
1564 panel_bg_color = rgb;
1565 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1567 panel_bg_color = panel_bg_color_save;
1571 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1572 if(tm.team != NUM_SPECTATOR)
1574 // display it anyway
1575 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1578 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1580 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1581 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1583 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1584 if(race_speedaward) {
1585 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);
1586 pos.y += 1.25 * hud_fontsize.y;
1588 if(race_speedaward_alltimebest) {
1589 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);
1590 pos.y += 1.25 * hud_fontsize.y;
1592 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1595 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1598 for(pl = players.sort_next; pl; pl = pl.sort_next)
1600 if(pl.team == NUM_SPECTATOR)
1602 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1603 if(tm.team == NUM_SPECTATOR)
1605 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1606 draw_beginBoldFont();
1607 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1609 pos.y += 1.25 * hud_fontsize.y;
1611 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1612 pos.y += 1.25 * hud_fontsize.y;
1618 // Print info string
1620 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1621 tl = STAT(TIMELIMIT);
1622 fl = STAT(FRAGLIMIT);
1623 ll = STAT(LEADLIMIT);
1624 if(gametype == MAPINFO_TYPE_LMS)
1627 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1632 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1636 str = strcat(str, _(" or"));
1639 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1640 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1641 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1642 TranslateScoresLabel(teamscores_label(ts_primary))));
1646 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1647 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1648 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1649 TranslateScoresLabel(scores_label(ps_primary))));
1654 if(tl > 0 || fl > 0)
1655 str = strcat(str, _(" or"));
1658 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1659 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1660 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1661 TranslateScoresLabel(teamscores_label(ts_primary))));
1665 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1666 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1667 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1668 TranslateScoresLabel(scores_label(ps_primary))));
1673 pos.y += 1.2 * hud_fontsize.y;
1674 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1676 // print information about respawn status
1677 float respawn_time = STAT(RESPAWN_TIME);
1681 if(respawn_time < 0)
1683 // a negative number means we are awaiting respawn, time value is still the same
1684 respawn_time *= -1; // remove mark now that we checked it
1686 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1687 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1689 str = sprintf(_("^1Respawning in ^3%s^1..."),
1690 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1691 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1693 count_seconds(ceil(respawn_time - time))
1697 else if(time < respawn_time)
1699 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1700 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1701 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1703 count_seconds(ceil(respawn_time - time))
1707 else if(time >= respawn_time)
1708 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1710 pos.y += 1.2 * hud_fontsize.y;
1711 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1714 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;