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/stats.qh>
10 #include <common/teams.qh>
14 const int MAX_SBT_FIELDS = MAX_SCORE;
16 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
17 float sbt_field_size[MAX_SBT_FIELDS + 1];
18 string sbt_field_title[MAX_SBT_FIELDS + 1];
21 string autocvar_hud_fontsize;
22 string hud_fontsize_str;
27 float sbt_fg_alpha_self;
29 float sbt_highlight_alpha;
30 float sbt_highlight_alpha_self;
32 // provide basic panel cvars to old clients
33 // TODO remove them after a future release (0.8.2+)
34 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
35 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
36 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
37 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
38 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
39 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
40 noref string autocvar_hud_panel_scoreboard_bg_border = "";
41 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
43 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
44 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
45 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
46 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
47 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
49 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
50 bool autocvar_hud_panel_scoreboard_table_highlight = true;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
52 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
53 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
54 float autocvar_hud_panel_scoreboard_namesize = 15;
56 bool autocvar_hud_panel_scoreboard_accuracy = true;
57 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
58 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
61 bool autocvar_hud_panel_scoreboard_dynamichud = false;
63 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
64 bool autocvar_hud_panel_scoreboard_others_showscore = true;
65 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
66 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
67 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
70 void drawstringright(vector, string, vector, vector, float, float);
71 void drawstringcenter(vector, string, vector, vector, float, float);
73 // wrapper to put all possible scores titles through gettext
74 string TranslateScoresLabel(string l)
78 case "bckills": return CTX(_("SCO^bckills"));
79 case "bctime": return CTX(_("SCO^bctime"));
80 case "caps": return CTX(_("SCO^caps"));
81 case "captime": return CTX(_("SCO^captime"));
82 case "deaths": return CTX(_("SCO^deaths"));
83 case "destroyed": return CTX(_("SCO^destroyed"));
84 case "dmg": return CTX(_("SCO^damage"));
85 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
86 case "drops": return CTX(_("SCO^drops"));
87 case "faults": return CTX(_("SCO^faults"));
88 case "fckills": return CTX(_("SCO^fckills"));
89 case "goals": return CTX(_("SCO^goals"));
90 case "kckills": return CTX(_("SCO^kckills"));
91 case "kdratio": return CTX(_("SCO^kdratio"));
92 case "kd": return CTX(_("SCO^k/d"));
93 case "kdr": return CTX(_("SCO^kdr"));
94 case "kills": return CTX(_("SCO^kills"));
95 case "laps": return CTX(_("SCO^laps"));
96 case "lives": return CTX(_("SCO^lives"));
97 case "losses": return CTX(_("SCO^losses"));
98 case "name": return CTX(_("SCO^name"));
99 case "sum": return CTX(_("SCO^sum"));
100 case "nick": return CTX(_("SCO^nick"));
101 case "objectives": return CTX(_("SCO^objectives"));
102 case "pickups": return CTX(_("SCO^pickups"));
103 case "ping": return CTX(_("SCO^ping"));
104 case "pl": return CTX(_("SCO^pl"));
105 case "pushes": return CTX(_("SCO^pushes"));
106 case "rank": return CTX(_("SCO^rank"));
107 case "returns": return CTX(_("SCO^returns"));
108 case "revivals": return CTX(_("SCO^revivals"));
109 case "rounds": return CTX(_("SCO^rounds won"));
110 case "score": return CTX(_("SCO^score"));
111 case "suicides": return CTX(_("SCO^suicides"));
112 case "takes": return CTX(_("SCO^takes"));
113 case "ticks": return CTX(_("SCO^ticks"));
118 void Scoreboard_InitScores()
122 ps_primary = ps_secondary = NULL;
123 ts_primary = ts_secondary = -1;
124 FOREACH(Scores, true, {
125 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
126 if(f == SFL_SORT_PRIO_PRIMARY)
128 if(f == SFL_SORT_PRIO_SECONDARY)
131 if(ps_secondary == NULL)
132 ps_secondary = ps_primary;
134 for(i = 0; i < MAX_TEAMSCORE; ++i)
136 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
137 if(f == SFL_SORT_PRIO_PRIMARY)
139 if(f == SFL_SORT_PRIO_SECONDARY)
142 if(ts_secondary == -1)
143 ts_secondary = ts_primary;
145 Cmd_Scoreboard_SetFields(0);
148 float SetTeam(entity pl, float Team);
150 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
296 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
297 LOG_INFO(_("Usage:\n"));
298 LOG_INFO(_("^2scoreboard_columns_set default\n"));
299 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
300 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
301 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
304 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
305 LOG_INFO(_("^3ping^7 Ping time\n"));
306 LOG_INFO(_("^3pl^7 Packet loss\n"));
307 LOG_INFO(_("^3elo^7 Player ELO\n"));
308 LOG_INFO(_("^3kills^7 Number of kills\n"));
309 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
310 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
311 LOG_INFO(_("^3frags^7 kills - suicides\n"));
312 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
313 LOG_INFO(_("^3dmg^7 The total damage done\n"));
314 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
315 LOG_INFO(_("^3sum^7 frags - deaths\n"));
316 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
317 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
318 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
319 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
320 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
321 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
322 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
323 LOG_INFO(_("^3rank^7 Player rank\n"));
324 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
325 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
326 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
327 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
328 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
329 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
330 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
331 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
332 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
333 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
334 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
335 LOG_INFO(_("^3score^7 Total score\n"));
338 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
339 "of game types, then a slash, to make the field show up only in these\n"
340 "or in all but these game types. You can also specify 'all' as a\n"
341 "field to show all fields available for the current game mode.\n\n"));
343 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
344 "include/exclude ALL teams/noteams game modes.\n\n"));
346 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
347 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
348 "right of the vertical bar aligned to the right.\n"));
349 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
350 "other gamemodes except DM.\n"));
353 // NOTE: adding a gametype with ? to not warn for an optional field
354 // make sure it's excluded in a previous exclusive rule, if any
355 // otherwise the previous exclusive rule warns anyway
356 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
357 #define SCOREBOARD_DEFAULT_COLUMNS \
359 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
360 " -teams,lms/deaths +ft,tdm/deaths" \
361 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
362 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
363 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
364 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
365 " +lms/lives +lms/rank" \
366 " +kh/caps +kh/pushes +kh/destroyed" \
367 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
368 " +as/objectives +nb/faults +nb/goals" \
369 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
370 " -lms,rc,cts,inv,nb/score"
372 void Cmd_Scoreboard_SetFields(int argc)
377 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
381 return; // do nothing, we don't know gametype and scores yet
383 // sbt_fields uses strunzone on the titles!
384 if(!sbt_field_title[0])
385 for(i = 0; i < MAX_SBT_FIELDS; ++i)
386 sbt_field_title[i] = strzone("(null)");
388 // TODO: re enable with gametype dependant cvars?
389 if(argc < 3) // no arguments provided
390 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
393 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 if(argv(2) == "default")
398 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
399 else if(argv(2) == "all")
402 s = "ping pl name |";
403 FOREACH(Scores, true, {
405 if(it != ps_secondary)
406 if(scores_label(it) != "")
407 s = strcat(s, " ", scores_label(it));
409 if(ps_secondary != ps_primary)
410 s = strcat(s, " ", scores_label(ps_secondary));
411 s = strcat(s, " ", scores_label(ps_primary));
412 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
419 hud_fontsize = HUD_GetFontsize("hud_fontsize");
421 for(i = 1; i < argc - 1; ++i)
427 if(substring(str, 0, 1) == "?")
430 str = substring(str, 1, strlen(str) - 1);
433 slash = strstrofs(str, "/", 0);
436 pattern = substring(str, 0, slash);
437 str = substring(str, slash + 1, strlen(str) - (slash + 1));
439 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
443 strunzone(sbt_field_title[sbt_num_fields]);
444 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
445 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
446 str = strtolower(str);
451 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
452 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
453 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
454 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
455 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
456 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
457 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
458 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
459 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
462 FOREACH(Scores, true, {
463 if (str == strtolower(scores_label(it))) {
465 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
475 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
479 sbt_field[sbt_num_fields] = j;
482 if(j == ps_secondary)
483 have_secondary = true;
488 if(sbt_num_fields >= MAX_SBT_FIELDS)
492 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
494 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
495 have_secondary = true;
496 if(ps_primary == ps_secondary)
497 have_secondary = true;
498 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
500 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
504 strunzone(sbt_field_title[sbt_num_fields]);
505 for(i = sbt_num_fields; i > 0; --i)
507 sbt_field_title[i] = sbt_field_title[i-1];
508 sbt_field_size[i] = sbt_field_size[i-1];
509 sbt_field[i] = sbt_field[i-1];
511 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
512 sbt_field[0] = SP_NAME;
514 LOG_INFO("fixed missing field 'name'\n");
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 1; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[1] = strzone("|");
526 sbt_field[1] = SP_SEPARATOR;
527 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
529 LOG_INFO("fixed missing field '|'\n");
532 else if(!have_separator)
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone("|");
536 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
537 sbt_field[sbt_num_fields] = SP_SEPARATOR;
539 LOG_INFO("fixed missing field '|'\n");
543 strunzone(sbt_field_title[sbt_num_fields]);
544 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
545 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
546 sbt_field[sbt_num_fields] = ps_secondary;
548 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
552 strunzone(sbt_field_title[sbt_num_fields]);
553 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_primary;
557 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
561 sbt_field[sbt_num_fields] = SP_END;
565 vector sbt_field_rgb;
566 string sbt_field_icon0;
567 string sbt_field_icon1;
568 string sbt_field_icon2;
569 vector sbt_field_icon0_rgb;
570 vector sbt_field_icon1_rgb;
571 vector sbt_field_icon2_rgb;
572 string Scoreboard_GetName(entity pl)
574 if(ready_waiting && pl.ready)
576 sbt_field_icon0 = "gfx/scoreboard/player_ready";
580 int f = entcs_GetClientColors(pl.sv_entnum);
582 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
583 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
584 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
585 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
586 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
589 return entcs_GetName(pl.sv_entnum);
591 string Scoreboard_GetField(entity pl, PlayerScoreField field)
593 float tmp, num, denom;
596 sbt_field_rgb = '1 1 1';
597 sbt_field_icon0 = "";
598 sbt_field_icon1 = "";
599 sbt_field_icon2 = "";
600 sbt_field_icon0_rgb = '1 1 1';
601 sbt_field_icon1_rgb = '1 1 1';
602 sbt_field_icon2_rgb = '1 1 1';
607 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
608 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
612 tmp = max(0, min(220, f-80)) / 220;
613 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
619 f = pl.ping_packetloss;
620 tmp = pl.ping_movementloss;
621 if(f == 0 && tmp == 0)
623 str = ftos(ceil(f * 100));
625 str = strcat(str, "~", ftos(ceil(tmp * 100)));
626 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
627 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
631 return Scoreboard_GetName(pl);
634 f = pl.(scores(SP_KILLS));
635 f -= pl.(scores(SP_SUICIDES));
639 num = pl.(scores(SP_KILLS));
640 denom = pl.(scores(SP_DEATHS));
643 sbt_field_rgb = '0 1 0';
644 str = sprintf("%d", num);
645 } else if(num <= 0) {
646 sbt_field_rgb = '1 0 0';
647 str = sprintf("%.1f", num/denom);
649 str = sprintf("%.1f", num/denom);
653 f = pl.(scores(SP_KILLS));
654 f -= pl.(scores(SP_DEATHS));
657 sbt_field_rgb = '0 1 0';
659 sbt_field_rgb = '1 1 1';
661 sbt_field_rgb = '1 0 0';
667 float elo = pl.(scores(SP_ELO));
669 case -1: return "...";
670 case -2: return _("N/A");
671 default: return ftos(elo);
675 case SP_DMG: case SP_DMGTAKEN:
676 return sprintf("%.1f k", pl.(scores(field)) / 1000);
678 default: case SP_SCORE:
679 tmp = pl.(scores(field));
680 f = scores_flags(field);
681 if(field == ps_primary)
682 sbt_field_rgb = '1 1 0';
683 else if(field == ps_secondary)
684 sbt_field_rgb = '0 1 1';
686 sbt_field_rgb = '1 1 1';
687 return ScoreString(f, tmp);
692 float sbt_fixcolumnwidth_len;
693 float sbt_fixcolumnwidth_iconlen;
694 float sbt_fixcolumnwidth_marginlen;
696 string Scoreboard_FixColumnWidth(int i, string str)
702 sbt_fixcolumnwidth_iconlen = 0;
704 if(sbt_field_icon0 != "")
706 sz = draw_getimagesize(sbt_field_icon0);
708 if(sbt_fixcolumnwidth_iconlen < f)
709 sbt_fixcolumnwidth_iconlen = f;
712 if(sbt_field_icon1 != "")
714 sz = draw_getimagesize(sbt_field_icon1);
716 if(sbt_fixcolumnwidth_iconlen < f)
717 sbt_fixcolumnwidth_iconlen = f;
720 if(sbt_field_icon2 != "")
722 sz = draw_getimagesize(sbt_field_icon2);
724 if(sbt_fixcolumnwidth_iconlen < f)
725 sbt_fixcolumnwidth_iconlen = f;
728 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
730 if(sbt_fixcolumnwidth_iconlen != 0)
731 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
733 sbt_fixcolumnwidth_marginlen = 0;
735 if(sbt_field[i] == SP_NAME) // name gets all remaining space
738 float remaining_space = 0;
739 for(j = 0; j < sbt_num_fields; ++j)
741 if (sbt_field[i] != SP_SEPARATOR)
742 remaining_space += sbt_field_size[j] + hud_fontsize.x;
743 sbt_field_size[i] = panel_size.x - remaining_space;
745 if (sbt_fixcolumnwidth_iconlen != 0)
746 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
747 float namesize = panel_size.x - remaining_space;
748 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
749 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
751 max_namesize = vid_conwidth - remaining_space;
754 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
756 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
757 if(sbt_field_size[i] < f)
758 sbt_field_size[i] = f;
763 void Scoreboard_initFieldSizes()
765 for(int i = 0; i < sbt_num_fields; ++i)
767 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
768 Scoreboard_FixColumnWidth(i, "");
772 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
775 vector column_dim = eY * panel_size.y;
777 column_dim.y -= 1.25 * hud_fontsize.y;
778 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
779 pos.x += hud_fontsize.x * 0.5;
780 for(i = 0; i < sbt_num_fields; ++i)
782 if(sbt_field[i] == SP_SEPARATOR)
784 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
787 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
788 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
789 pos.x += column_dim.x;
791 if(sbt_field[i] == SP_SEPARATOR)
793 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
794 for(i = sbt_num_fields - 1; i > 0; --i)
796 if(sbt_field[i] == SP_SEPARATOR)
799 pos.x -= sbt_field_size[i];
804 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
805 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
808 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
809 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
810 pos.x -= hud_fontsize.x;
815 pos.y += 1.25 * hud_fontsize.y;
819 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
821 TC(bool, is_self); TC(int, pl_number);
823 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
825 vector h_pos = item_pos;
826 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
827 // alternated rows highlighting
829 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
830 else if((sbt_highlight) && (!(pl_number % 2)))
831 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
833 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
835 vector pos = item_pos;
836 pos.x += hud_fontsize.x * 0.5;
837 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
838 vector tmp = '0 0 0';
840 PlayerScoreField field;
841 for(i = 0; i < sbt_num_fields; ++i)
843 field = sbt_field[i];
844 if(field == SP_SEPARATOR)
847 if(is_spec && field != SP_NAME && field != SP_PING) {
848 pos.x += sbt_field_size[i] + hud_fontsize.x;
851 str = Scoreboard_GetField(pl, field);
852 str = Scoreboard_FixColumnWidth(i, str);
854 pos.x += sbt_field_size[i] + hud_fontsize.x;
856 if(field == SP_NAME) {
857 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
858 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
860 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
861 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
864 tmp.x = sbt_field_size[i] + hud_fontsize.x;
865 if(sbt_field_icon0 != "")
866 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
867 if(sbt_field_icon1 != "")
868 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
869 if(sbt_field_icon2 != "")
870 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
873 if(sbt_field[i] == SP_SEPARATOR)
875 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
876 for(i = sbt_num_fields-1; i > 0; --i)
878 field = sbt_field[i];
879 if(field == SP_SEPARATOR)
882 if(is_spec && field != SP_NAME && field != SP_PING) {
883 pos.x -= sbt_field_size[i] + hud_fontsize.x;
887 str = Scoreboard_GetField(pl, field);
888 str = Scoreboard_FixColumnWidth(i, str);
890 if(field == SP_NAME) {
891 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
892 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
894 tmp.x = sbt_fixcolumnwidth_len;
895 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_field_size[i];
899 if(sbt_field_icon0 != "")
900 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
901 if(sbt_field_icon1 != "")
902 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
903 if(sbt_field_icon2 != "")
904 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
905 pos.x -= sbt_field_size[i] + hud_fontsize.x;
910 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
913 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
916 vector h_pos = item_pos;
917 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
919 bool complete = (this_team == NUM_SPECTATOR);
922 if((sbt_highlight) && (!(pl_number % 2)))
923 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
925 vector pos = item_pos;
926 pos.x += hud_fontsize.x * 0.5;
927 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
929 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
931 width_limit -= stringwidth("...", false, hud_fontsize);
932 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
933 static float max_name_width = 0;
936 float min_fieldsize = 0;
937 float fieldpadding = hud_fontsize.x * 0.25;
938 if(this_team == NUM_SPECTATOR)
940 if(autocvar_hud_panel_scoreboard_spectators_showping)
941 min_fieldsize = stringwidth("999", false, hud_fontsize);
943 else if(autocvar_hud_panel_scoreboard_others_showscore)
944 min_fieldsize = stringwidth("99", false, hud_fontsize);
945 for(i = 0; pl; pl = pl.sort_next)
947 if(pl.team != this_team)
953 if(this_team == NUM_SPECTATOR)
955 if(autocvar_hud_panel_scoreboard_spectators_showping)
956 field = Scoreboard_GetField(pl, SP_PING);
958 else if(autocvar_hud_panel_scoreboard_others_showscore)
959 field = Scoreboard_GetField(pl, SP_SCORE);
961 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
962 float column_width = stringwidth(str, true, hud_fontsize);
963 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
965 if(column_width > max_name_width)
966 max_name_width = column_width;
967 column_width = max_name_width;
971 fieldsize = stringwidth(field, false, hud_fontsize);
972 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
975 if(pos.x + column_width > width_limit)
980 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
985 pos.x = item_pos.x + hud_fontsize.x * 0.5;
986 pos.y += hud_fontsize.y * 1.25;
990 vector name_pos = pos;
991 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
992 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
993 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
996 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
997 h_size.y = hud_fontsize.y;
998 vector field_pos = pos;
999 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1000 field_pos.x += column_width - h_size.x;
1002 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1003 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1004 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1006 pos.x += column_width;
1007 pos.x += hud_fontsize.x;
1009 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
1012 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1014 int max_players = 999;
1015 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1017 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1020 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1021 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1022 height /= team_count;
1025 height -= panel_bg_padding * 2; // - padding
1026 max_players = floor(height / (hud_fontsize.y * 1.25));
1027 if(max_players <= 1)
1029 if(max_players == tm.team_size)
1034 entity me = playerslots[current_player];
1036 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1037 panel_size.y += panel_bg_padding * 2;
1040 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1041 if(panel.current_panel_bg != "0")
1042 end_pos.y += panel_bg_border * 2;
1044 if(panel_bg_padding)
1046 panel_pos += '1 1 0' * panel_bg_padding;
1047 panel_size -= '2 2 0' * panel_bg_padding;
1051 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1055 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1057 pos.y += 1.25 * hud_fontsize.y;
1060 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1062 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1065 // print header row and highlight columns
1066 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1068 // fill the table and draw the rows
1069 bool is_self = false;
1070 bool self_shown = false;
1072 for(pl = players.sort_next; pl; pl = pl.sort_next)
1074 if(pl.team != tm.team)
1076 if(i == max_players - 2 && pl != me)
1078 if(!self_shown && me.team == tm.team)
1080 Scoreboard_DrawItem(pos, rgb, me, true, i);
1082 pos.y += 1.25 * hud_fontsize.y;
1086 if(i >= max_players - 1)
1088 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1091 is_self = (pl.sv_entnum == current_player);
1092 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1095 pos.y += 1.25 * hud_fontsize.y;
1099 panel_size.x += panel_bg_padding * 2; // restore initial width
1103 bool Scoreboard_WouldDraw()
1105 if (MUTATOR_CALLHOOK(DrawScoreboard))
1107 else if (QuickMenu_IsOpened())
1109 else if (HUD_Radar_Clickable())
1111 else if (scoreboard_showscores)
1113 else if (intermission == 1)
1115 else if (intermission == 2)
1117 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1119 else if (scoreboard_showscores_force)
1124 float average_accuracy;
1125 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1127 WepSet weapons_stat = WepSet_GetFromStat();
1128 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1129 int disownedcnt = 0;
1131 FOREACH(Weapons, it != WEP_Null, {
1132 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1134 WepSet set = it.m_wepset;
1135 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1137 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1144 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1145 if (weapon_cnt <= 0) return pos;
1148 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1150 int columnns = ceil(weapon_cnt / rows);
1152 float weapon_height = 29;
1153 float height = hud_fontsize.y + weapon_height;
1155 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1156 pos.y += 1.25 * hud_fontsize.y;
1157 if(panel.current_panel_bg != "0")
1158 pos.y += panel_bg_border;
1161 panel_size.y = height * rows;
1162 panel_size.y += panel_bg_padding * 2;
1165 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1166 if(panel.current_panel_bg != "0")
1167 end_pos.y += panel_bg_border * 2;
1169 if(panel_bg_padding)
1171 panel_pos += '1 1 0' * panel_bg_padding;
1172 panel_size -= '2 2 0' * panel_bg_padding;
1176 vector tmp = panel_size;
1178 float weapon_width = tmp.x / columnns / rows;
1181 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1185 // column highlighting
1186 for (int i = 0; i < columnns; ++i)
1188 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1191 for (int i = 0; i < rows; ++i)
1192 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1195 average_accuracy = 0;
1196 int weapons_with_stats = 0;
1198 pos.x += weapon_width / 2;
1200 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1203 Accuracy_LoadColors();
1205 float oldposx = pos.x;
1209 FOREACH(Weapons, it != WEP_Null, {
1210 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1212 WepSet set = it.m_wepset;
1213 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1217 if (weapon_stats >= 0)
1218 weapon_alpha = sbt_fg_alpha;
1220 weapon_alpha = 0.2 * sbt_fg_alpha;
1223 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1225 if (weapon_stats >= 0) {
1226 weapons_with_stats += 1;
1227 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1230 s = sprintf("%d%%", weapon_stats * 100);
1233 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1235 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1236 rgb = Accuracy_GetColor(weapon_stats);
1238 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1240 tmpos.x += weapon_width * rows;
1241 pos.x += weapon_width * rows;
1242 if (rows == 2 && column == columnns - 1) {
1250 if (weapons_with_stats)
1251 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1253 panel_size.x += panel_bg_padding * 2; // restore initial width
1257 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1259 pos.x += hud_fontsize.x * 0.25;
1260 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1261 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1262 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1264 pos.y += hud_fontsize.y;
1269 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1270 float stat_secrets_found, stat_secrets_total;
1271 float stat_monsters_killed, stat_monsters_total;
1275 // get monster stats
1276 stat_monsters_killed = STAT(MONSTERS_KILLED);
1277 stat_monsters_total = STAT(MONSTERS_TOTAL);
1279 // get secrets stats
1280 stat_secrets_found = STAT(SECRETS_FOUND);
1281 stat_secrets_total = STAT(SECRETS_TOTAL);
1283 // get number of rows
1284 if(stat_secrets_total)
1286 if(stat_monsters_total)
1289 // if no rows, return
1293 // draw table header
1294 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1295 pos.y += 1.25 * hud_fontsize.y;
1296 if(panel.current_panel_bg != "0")
1297 pos.y += panel_bg_border;
1300 panel_size.y = hud_fontsize.y * rows;
1301 panel_size.y += panel_bg_padding * 2;
1304 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1305 if(panel.current_panel_bg != "0")
1306 end_pos.y += panel_bg_border * 2;
1308 if(panel_bg_padding)
1310 panel_pos += '1 1 0' * panel_bg_padding;
1311 panel_size -= '2 2 0' * panel_bg_padding;
1315 vector tmp = panel_size;
1318 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1321 if(stat_monsters_total)
1323 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1324 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1328 if(stat_secrets_total)
1330 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1331 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1334 panel_size.x += panel_bg_padding * 2; // restore initial width
1339 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1342 RANKINGS_RECEIVED_CNT = 0;
1343 for (i=RANKINGS_CNT-1; i>=0; --i)
1345 ++RANKINGS_RECEIVED_CNT;
1347 if (RANKINGS_RECEIVED_CNT == 0)
1350 vector hl_rgb = rgb + '0.5 0.5 0.5';
1352 pos.y += hud_fontsize.y;
1353 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1354 pos.y += 1.25 * hud_fontsize.y;
1355 if(panel.current_panel_bg != "0")
1356 pos.y += panel_bg_border;
1361 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1363 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1368 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1370 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1374 float ranksize = 3 * hud_fontsize.x;
1375 float timesize = 5 * hud_fontsize.x;
1376 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1377 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1378 columns = min(columns, RANKINGS_RECEIVED_CNT);
1380 // expand name column to fill the entire row
1381 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1382 namesize += available_space;
1383 columnsize.x += available_space;
1385 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1386 panel_size.y += panel_bg_padding * 2;
1390 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1391 if(panel.current_panel_bg != "0")
1392 end_pos.y += panel_bg_border * 2;
1394 if(panel_bg_padding)
1396 panel_pos += '1 1 0' * panel_bg_padding;
1397 panel_size -= '2 2 0' * panel_bg_padding;
1403 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1405 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1407 int column = 0, j = 0;
1408 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1415 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1416 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1417 else if(!((j + column) & 1) && sbt_highlight)
1418 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1420 str = count_ordinal(i+1);
1421 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1422 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1423 str = ColorTranslateRGB(grecordholder[i]);
1425 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1426 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1428 pos.y += 1.25 * hud_fontsize.y;
1430 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1434 pos.x += panel_size.x / columns;
1435 pos.y = panel_pos.y;
1439 panel_size.x += panel_bg_padding * 2; // restore initial width
1443 void Scoreboard_Draw()
1445 if(!autocvar__hud_configure)
1447 if(!hud_draw_maximized) return;
1449 // frametime checks allow to toggle the scoreboard even when the game is paused
1450 if(scoreboard_active) {
1451 if(hud_configure_menu_open == 1)
1452 scoreboard_fade_alpha = 1;
1453 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1454 if (scoreboard_fadeinspeed && frametime)
1455 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1457 scoreboard_fade_alpha = 1;
1458 if(hud_fontsize_str != autocvar_hud_fontsize)
1460 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1461 Scoreboard_initFieldSizes();
1462 if(hud_fontsize_str)
1463 strunzone(hud_fontsize_str);
1464 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1468 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1469 if (scoreboard_fadeoutspeed && frametime)
1470 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1472 scoreboard_fade_alpha = 0;
1475 if (!scoreboard_fade_alpha)
1479 scoreboard_fade_alpha = 0;
1481 if (autocvar_hud_panel_scoreboard_dynamichud)
1484 HUD_Scale_Disable();
1486 if(scoreboard_fade_alpha <= 0)
1488 panel_fade_alpha *= scoreboard_fade_alpha;
1489 HUD_Panel_LoadCvars();
1491 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1492 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1493 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1494 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1495 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1496 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1498 // don't overlap with con_notify
1499 if(!autocvar__hud_configure)
1500 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1502 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1503 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1504 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1505 panel_size.x = fixed_scoreboard_width;
1507 Scoreboard_UpdatePlayerTeams();
1509 vector pos = panel_pos;
1514 vector sb_heading_fontsize;
1515 sb_heading_fontsize = hud_fontsize * 2;
1516 draw_beginBoldFont();
1517 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1520 pos.y += sb_heading_fontsize.y;
1521 if(panel.current_panel_bg != "0")
1522 pos.y += panel_bg_border;
1524 // Draw the scoreboard
1525 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1528 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1532 vector panel_bg_color_save = panel_bg_color;
1533 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1534 if(panel.current_panel_bg != "0")
1535 team_score_baseoffset.x -= panel_bg_border;
1536 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1538 if(tm.team == NUM_SPECTATOR)
1543 draw_beginBoldFont();
1544 vector rgb = Team_ColorRGB(tm.team);
1545 str = ftos(tm.(teamscores(ts_primary)));
1546 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1548 if(ts_primary != ts_secondary)
1550 str = ftos(tm.(teamscores(ts_secondary)));
1551 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1554 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1555 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1556 else if(panel_bg_color_team > 0)
1557 panel_bg_color = rgb * panel_bg_color_team;
1559 panel_bg_color = rgb;
1560 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1562 panel_bg_color = panel_bg_color_save;
1566 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1567 if(tm.team != NUM_SPECTATOR)
1569 // display it anyway
1570 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1573 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1575 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1576 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1578 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1579 if(race_speedaward) {
1580 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);
1581 pos.y += 1.25 * hud_fontsize.y;
1583 if(race_speedaward_alltimebest) {
1584 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);
1585 pos.y += 1.25 * hud_fontsize.y;
1587 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1590 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1593 for(pl = players.sort_next; pl; pl = pl.sort_next)
1595 if(pl.team == NUM_SPECTATOR)
1597 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1598 if(tm.team == NUM_SPECTATOR)
1600 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1601 draw_beginBoldFont();
1602 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1604 pos.y += 1.25 * hud_fontsize.y;
1606 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1607 pos.y += 1.25 * hud_fontsize.y;
1613 // Print info string
1615 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1616 tl = STAT(TIMELIMIT);
1617 fl = STAT(FRAGLIMIT);
1618 ll = STAT(LEADLIMIT);
1619 if(gametype == MAPINFO_TYPE_LMS)
1622 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1627 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1631 str = strcat(str, _(" or"));
1634 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1635 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1636 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1637 TranslateScoresLabel(teamscores_label(ts_primary))));
1641 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1642 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1643 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1644 TranslateScoresLabel(scores_label(ps_primary))));
1649 if(tl > 0 || fl > 0)
1650 str = strcat(str, _(" or"));
1653 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1654 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1655 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1656 TranslateScoresLabel(teamscores_label(ts_primary))));
1660 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1661 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1662 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1663 TranslateScoresLabel(scores_label(ps_primary))));
1668 pos.y += 1.2 * hud_fontsize.y;
1669 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1671 // print information about respawn status
1672 float respawn_time = STAT(RESPAWN_TIME);
1676 if(respawn_time < 0)
1678 // a negative number means we are awaiting respawn, time value is still the same
1679 respawn_time *= -1; // remove mark now that we checked it
1681 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1682 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1684 str = sprintf(_("^1Respawning in ^3%s^1..."),
1685 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1686 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1688 count_seconds(ceil(respawn_time - time))
1692 else if(time < respawn_time)
1694 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1695 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1696 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1698 count_seconds(ceil(respawn_time - time))
1702 else if(time >= respawn_time)
1703 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1705 pos.y += 1.2 * hud_fontsize.y;
1706 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1709 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;