1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/miscfunctions.qh>
6 #include "quickmenu.qh"
7 #include <common/ent_cs.qh>
8 #include <common/constants.qh>
9 #include <common/net_linked.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/scores.qh>
13 #include <common/stats.qh>
14 #include <common/teams.qh>
18 const int MAX_SBT_FIELDS = MAX_SCORE;
20 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
21 float sbt_field_size[MAX_SBT_FIELDS + 1];
22 string sbt_field_title[MAX_SBT_FIELDS + 1];
25 string autocvar_hud_fontsize;
26 string hud_fontsize_str;
31 float sbt_fg_alpha_self;
33 float sbt_highlight_alpha;
34 float sbt_highlight_alpha_self;
36 // provide basic panel cvars to old clients
37 // TODO remove them after a future release (0.8.2+)
38 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
39 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
40 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
41 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
42 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
43 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
44 noref string autocvar_hud_panel_scoreboard_bg_border = "";
45 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
47 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
48 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
49 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
50 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
51 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
52 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
54 bool autocvar_hud_panel_scoreboard_table_highlight = true;
55 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
57 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
58 float autocvar_hud_panel_scoreboard_namesize = 15;
60 bool autocvar_hud_panel_scoreboard_accuracy = true;
61 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
62 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
63 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
65 bool autocvar_hud_panel_scoreboard_dynamichud = false;
67 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
68 bool autocvar_hud_panel_scoreboard_others_showscore = true;
69 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
70 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
71 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 void drawstringright(vector, string, vector, vector, float, float);
75 void drawstringcenter(vector, string, vector, vector, float, float);
77 // wrapper to put all possible scores titles through gettext
78 string TranslateScoresLabel(string l)
82 case "bckills": return CTX(_("SCO^bckills"));
83 case "bctime": return CTX(_("SCO^bctime"));
84 case "caps": return CTX(_("SCO^caps"));
85 case "captime": return CTX(_("SCO^captime"));
86 case "deaths": return CTX(_("SCO^deaths"));
87 case "destroyed": return CTX(_("SCO^destroyed"));
88 case "dmg": return CTX(_("SCO^damage"));
89 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
90 case "drops": return CTX(_("SCO^drops"));
91 case "faults": return CTX(_("SCO^faults"));
92 case "fckills": return CTX(_("SCO^fckills"));
93 case "goals": return CTX(_("SCO^goals"));
94 case "kckills": return CTX(_("SCO^kckills"));
95 case "kdratio": return CTX(_("SCO^kdratio"));
96 case "kd": return CTX(_("SCO^k/d"));
97 case "kdr": return CTX(_("SCO^kdr"));
98 case "kills": return CTX(_("SCO^kills"));
99 case "laps": return CTX(_("SCO^laps"));
100 case "lives": return CTX(_("SCO^lives"));
101 case "losses": return CTX(_("SCO^losses"));
102 case "name": return CTX(_("SCO^name"));
103 case "sum": return CTX(_("SCO^sum"));
104 case "nick": return CTX(_("SCO^nick"));
105 case "objectives": return CTX(_("SCO^objectives"));
106 case "pickups": return CTX(_("SCO^pickups"));
107 case "ping": return CTX(_("SCO^ping"));
108 case "pl": return CTX(_("SCO^pl"));
109 case "pushes": return CTX(_("SCO^pushes"));
110 case "rank": return CTX(_("SCO^rank"));
111 case "returns": return CTX(_("SCO^returns"));
112 case "revivals": return CTX(_("SCO^revivals"));
113 case "rounds": return CTX(_("SCO^rounds won"));
114 case "score": return CTX(_("SCO^score"));
115 case "suicides": return CTX(_("SCO^suicides"));
116 case "takes": return CTX(_("SCO^takes"));
117 case "ticks": return CTX(_("SCO^ticks"));
122 void Scoreboard_InitScores()
126 ps_primary = ps_secondary = NULL;
127 ts_primary = ts_secondary = -1;
128 FOREACH(Scores, true, {
129 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
130 if(f == SFL_SORT_PRIO_PRIMARY)
132 if(f == SFL_SORT_PRIO_SECONDARY)
135 if(ps_secondary == NULL)
136 ps_secondary = ps_primary;
138 for(i = 0; i < MAX_TEAMSCORE; ++i)
140 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
141 if(f == SFL_SORT_PRIO_PRIMARY)
143 if(f == SFL_SORT_PRIO_SECONDARY)
146 if(ts_secondary == -1)
147 ts_secondary = ts_primary;
149 Cmd_Scoreboard_SetFields(0);
152 float SetTeam(entity pl, float Team);
154 void Scoreboard_UpdatePlayerTeams()
158 for(pl = players.sort_next; pl; pl = pl.sort_next)
161 int Team = entcs_GetScoreTeam(pl.sv_entnum);
162 if(SetTeam(pl, Team))
165 Scoreboard_UpdatePlayerPos(pl);
169 pl = players.sort_next;
174 print(strcat("PNUM: ", ftos(num), "\n"));
179 int Scoreboard_CompareScore(int vl, int vr, int f)
181 TC(int, vl); TC(int, vr); TC(int, f);
182 if(f & SFL_ZERO_IS_WORST)
184 if(vl == 0 && vr != 0)
186 if(vl != 0 && vr == 0)
190 return IS_INCREASING(f);
192 return IS_DECREASING(f);
196 float Scoreboard_ComparePlayerScores(entity left, entity right)
199 vl = entcs_GetTeam(left.sv_entnum);
200 vr = entcs_GetTeam(right.sv_entnum);
212 if(vl == NUM_SPECTATOR)
214 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
216 if(!left.gotscores && right.gotscores)
221 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
225 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
229 FOREACH(Scores, true, {
230 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
231 if (r >= 0) return r;
234 if (left.sv_entnum < right.sv_entnum)
240 void Scoreboard_UpdatePlayerPos(entity player)
243 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
245 SORT_SWAP(player, ent);
247 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
249 SORT_SWAP(ent, player);
253 float Scoreboard_CompareTeamScores(entity left, entity right)
257 if(left.team == NUM_SPECTATOR)
259 if(right.team == NUM_SPECTATOR)
262 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
266 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
270 for(i = 0; i < MAX_TEAMSCORE; ++i)
272 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
277 if (left.team < right.team)
283 void Scoreboard_UpdateTeamPos(entity Team)
286 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
288 SORT_SWAP(Team, ent);
290 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
292 SORT_SWAP(ent, Team);
296 void Cmd_Scoreboard_Help()
298 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
299 LOG_INFO(_("^3|---------------------------------------------------------------|"));
300 LOG_INFO(_("Usage:"));
301 LOG_INFO(_("^2scoreboard_columns_set default"));
302 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
303 LOG_INFO(_("The following field names are recognized (case insensitive):"));
304 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
307 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player"));
308 LOG_INFO(_("^3ping^7 Ping time"));
309 LOG_INFO(_("^3pl^7 Packet loss"));
310 LOG_INFO(_("^3elo^7 Player ELO"));
311 LOG_INFO(_("^3kills^7 Number of kills"));
312 LOG_INFO(_("^3deaths^7 Number of deaths"));
313 LOG_INFO(_("^3suicides^7 Number of suicides"));
314 LOG_INFO(_("^3frags^7 kills - suicides"));
315 LOG_INFO(_("^3kd^7 The kill-death ratio"));
316 LOG_INFO(_("^3dmg^7 The total damage done"));
317 LOG_INFO(_("^3dmgtaken^7 The total damage taken"));
318 LOG_INFO(_("^3sum^7 frags - deaths"));
319 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured"));
320 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
321 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)"));
322 LOG_INFO(_("^3fckills^7 Number of flag carrier kills"));
323 LOG_INFO(_("^3returns^7 Number of flag returns"));
324 LOG_INFO(_("^3drops^7 Number of flag drops"));
325 LOG_INFO(_("^3lives^7 Number of lives (LMS)"));
326 LOG_INFO(_("^3rank^7 Player rank"));
327 LOG_INFO(_("^3pushes^7 Number of players pushed into void"));
328 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void"));
329 LOG_INFO(_("^3kckills^7 Number of keys carrier kills"));
330 LOG_INFO(_("^3losses^7 Number of times a key was lost"));
331 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)"));
332 LOG_INFO(_("^3time^7 Total time raced (race/cts)"));
333 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)"));
334 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)"));
335 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)"));
336 LOG_INFO(_("^3bckills^7 Number of ball carrier kills"));
337 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway"));
338 LOG_INFO(_("^3score^7 Total score"));
341 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
342 "of game types, then a slash, to make the field show up only in these\n"
343 "or in all but these game types. You can also specify 'all' as a\n"
344 "field to show all fields available for the current game mode.\n\n"));
346 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
347 "include/exclude ALL teams/noteams game modes.\n\n"));
349 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
350 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields"
351 "right of the vertical bar aligned to the right.\n"));
352 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\nother gamemodes except DM.\n"));
355 // NOTE: adding a gametype with ? to not warn for an optional field
356 // make sure it's excluded in a previous exclusive rule, if any
357 // otherwise the previous exclusive rule warns anyway
358 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
359 #define SCOREBOARD_DEFAULT_COLUMNS \
361 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
362 " -teams,lms/deaths +ft,tdm/deaths" \
363 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
364 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
365 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
366 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
367 " +lms/lives +lms/rank" \
368 " +kh/caps +kh/pushes +kh/destroyed" \
369 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
370 " +as/objectives +nb/faults +nb/goals" \
371 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
372 " -lms,rc,cts,inv,nb/score"
374 void Cmd_Scoreboard_SetFields(int argc)
379 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
383 return; // do nothing, we don't know gametype and scores yet
385 // sbt_fields uses strunzone on the titles!
386 if(!sbt_field_title[0])
387 for(i = 0; i < MAX_SBT_FIELDS; ++i)
388 sbt_field_title[i] = strzone("(null)");
390 // TODO: re enable with gametype dependant cvars?
391 if(argc < 3) // no arguments provided
392 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
395 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
399 if(argv(2) == "default")
400 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
401 else if(argv(2) == "all")
404 s = "ping pl name |";
405 FOREACH(Scores, true, {
407 if(it != ps_secondary)
408 if(scores_label(it) != "")
409 s = strcat(s, " ", scores_label(it));
411 if(ps_secondary != ps_primary)
412 s = strcat(s, " ", scores_label(ps_secondary));
413 s = strcat(s, " ", scores_label(ps_primary));
414 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
421 hud_fontsize = HUD_GetFontsize("hud_fontsize");
423 for(i = 1; i < argc - 1; ++i)
429 if(substring(str, 0, 1) == "?")
432 str = substring(str, 1, strlen(str) - 1);
435 slash = strstrofs(str, "/", 0);
438 pattern = substring(str, 0, slash);
439 str = substring(str, slash + 1, strlen(str) - (slash + 1));
441 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
445 strunzone(sbt_field_title[sbt_num_fields]);
446 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
447 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
448 str = strtolower(str);
453 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
454 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
455 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
456 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
457 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
458 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
459 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
460 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
461 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
464 FOREACH(Scores, true, {
465 if (str == strtolower(scores_label(it))) {
467 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
477 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
481 sbt_field[sbt_num_fields] = j;
484 if(j == ps_secondary)
485 have_secondary = true;
490 if(sbt_num_fields >= MAX_SBT_FIELDS)
494 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
496 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
497 have_secondary = true;
498 if(ps_primary == ps_secondary)
499 have_secondary = true;
500 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
502 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
506 strunzone(sbt_field_title[sbt_num_fields]);
507 for(i = sbt_num_fields; i > 0; --i)
509 sbt_field_title[i] = sbt_field_title[i-1];
510 sbt_field_size[i] = sbt_field_size[i-1];
511 sbt_field[i] = sbt_field[i-1];
513 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
514 sbt_field[0] = SP_NAME;
516 LOG_INFO("fixed missing field 'name'");
520 strunzone(sbt_field_title[sbt_num_fields]);
521 for(i = sbt_num_fields; i > 1; --i)
523 sbt_field_title[i] = sbt_field_title[i-1];
524 sbt_field_size[i] = sbt_field_size[i-1];
525 sbt_field[i] = sbt_field[i-1];
527 sbt_field_title[1] = strzone("|");
528 sbt_field[1] = SP_SEPARATOR;
529 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
531 LOG_INFO("fixed missing field '|'");
534 else if(!have_separator)
536 strunzone(sbt_field_title[sbt_num_fields]);
537 sbt_field_title[sbt_num_fields] = strzone("|");
538 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
539 sbt_field[sbt_num_fields] = SP_SEPARATOR;
541 LOG_INFO("fixed missing field '|'");
545 strunzone(sbt_field_title[sbt_num_fields]);
546 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
547 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
548 sbt_field[sbt_num_fields] = ps_secondary;
550 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
554 strunzone(sbt_field_title[sbt_num_fields]);
555 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
556 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
557 sbt_field[sbt_num_fields] = ps_primary;
559 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
563 sbt_field[sbt_num_fields] = SP_END;
567 vector sbt_field_rgb;
568 string sbt_field_icon0;
569 string sbt_field_icon1;
570 string sbt_field_icon2;
571 vector sbt_field_icon0_rgb;
572 vector sbt_field_icon1_rgb;
573 vector sbt_field_icon2_rgb;
574 string Scoreboard_GetName(entity pl)
576 if(ready_waiting && pl.ready)
578 sbt_field_icon0 = "gfx/scoreboard/player_ready";
582 int f = entcs_GetClientColors(pl.sv_entnum);
584 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
585 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
586 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
587 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
588 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
591 return entcs_GetName(pl.sv_entnum);
593 string Scoreboard_GetField(entity pl, PlayerScoreField field)
595 float tmp, num, denom;
598 sbt_field_rgb = '1 1 1';
599 sbt_field_icon0 = "";
600 sbt_field_icon1 = "";
601 sbt_field_icon2 = "";
602 sbt_field_icon0_rgb = '1 1 1';
603 sbt_field_icon1_rgb = '1 1 1';
604 sbt_field_icon2_rgb = '1 1 1';
609 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
610 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
614 tmp = max(0, min(220, f-80)) / 220;
615 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
621 f = pl.ping_packetloss;
622 tmp = pl.ping_movementloss;
623 if(f == 0 && tmp == 0)
625 str = ftos(ceil(f * 100));
627 str = strcat(str, "~", ftos(ceil(tmp * 100)));
628 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
629 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
633 return Scoreboard_GetName(pl);
636 f = pl.(scores(SP_KILLS));
637 f -= pl.(scores(SP_SUICIDES));
641 num = pl.(scores(SP_KILLS));
642 denom = pl.(scores(SP_DEATHS));
645 sbt_field_rgb = '0 1 0';
646 str = sprintf("%d", num);
647 } else if(num <= 0) {
648 sbt_field_rgb = '1 0 0';
649 str = sprintf("%.1f", num/denom);
651 str = sprintf("%.1f", num/denom);
655 f = pl.(scores(SP_KILLS));
656 f -= pl.(scores(SP_DEATHS));
659 sbt_field_rgb = '0 1 0';
661 sbt_field_rgb = '1 1 1';
663 sbt_field_rgb = '1 0 0';
669 float elo = pl.(scores(SP_ELO));
671 case -1: return "...";
672 case -2: return _("N/A");
673 default: return ftos(elo);
677 case SP_DMG: case SP_DMGTAKEN:
678 return sprintf("%.1f k", pl.(scores(field)) / 1000);
680 default: case SP_SCORE:
681 tmp = pl.(scores(field));
682 f = scores_flags(field);
683 if(field == ps_primary)
684 sbt_field_rgb = '1 1 0';
685 else if(field == ps_secondary)
686 sbt_field_rgb = '0 1 1';
688 sbt_field_rgb = '1 1 1';
689 return ScoreString(f, tmp);
694 float sbt_fixcolumnwidth_len;
695 float sbt_fixcolumnwidth_iconlen;
696 float sbt_fixcolumnwidth_marginlen;
698 string Scoreboard_FixColumnWidth(int i, string str)
704 sbt_fixcolumnwidth_iconlen = 0;
706 if(sbt_field_icon0 != "")
708 sz = draw_getimagesize(sbt_field_icon0);
710 if(sbt_fixcolumnwidth_iconlen < f)
711 sbt_fixcolumnwidth_iconlen = f;
714 if(sbt_field_icon1 != "")
716 sz = draw_getimagesize(sbt_field_icon1);
718 if(sbt_fixcolumnwidth_iconlen < f)
719 sbt_fixcolumnwidth_iconlen = f;
722 if(sbt_field_icon2 != "")
724 sz = draw_getimagesize(sbt_field_icon2);
726 if(sbt_fixcolumnwidth_iconlen < f)
727 sbt_fixcolumnwidth_iconlen = f;
730 if(sbt_fixcolumnwidth_iconlen != 0)
732 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
733 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
736 sbt_fixcolumnwidth_marginlen = 0;
738 if(sbt_field[i] == SP_NAME) // name gets all remaining space
741 float remaining_space = 0;
742 for(j = 0; j < sbt_num_fields; ++j)
744 if (sbt_field[i] != SP_SEPARATOR)
745 remaining_space += sbt_field_size[j] + hud_fontsize.x;
746 sbt_field_size[i] = panel_size.x - remaining_space;
748 if (sbt_fixcolumnwidth_iconlen != 0)
749 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
750 float namesize = panel_size.x - remaining_space;
751 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
752 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
754 max_namesize = vid_conwidth - remaining_space;
757 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
759 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
760 if(sbt_field_size[i] < f)
761 sbt_field_size[i] = f;
766 void Scoreboard_initFieldSizes()
768 for(int i = 0; i < sbt_num_fields; ++i)
770 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
771 Scoreboard_FixColumnWidth(i, "");
775 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
778 vector column_dim = eY * panel_size.y;
780 column_dim.y -= 1.25 * hud_fontsize.y;
781 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
782 pos.x += hud_fontsize.x * 0.5;
783 for(i = 0; i < sbt_num_fields; ++i)
785 if(sbt_field[i] == SP_SEPARATOR)
787 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
790 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
791 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
792 pos.x += column_dim.x;
794 if(sbt_field[i] == SP_SEPARATOR)
796 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
797 for(i = sbt_num_fields - 1; i > 0; --i)
799 if(sbt_field[i] == SP_SEPARATOR)
802 pos.x -= sbt_field_size[i];
807 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
808 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
811 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
812 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
813 pos.x -= hud_fontsize.x;
818 pos.y += 1.25 * hud_fontsize.y;
822 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
824 TC(bool, is_self); TC(int, pl_number);
826 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
828 vector h_pos = item_pos;
829 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
830 // alternated rows highlighting
832 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
833 else if((sbt_highlight) && (!(pl_number % 2)))
834 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
836 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
838 vector pos = item_pos;
839 pos.x += hud_fontsize.x * 0.5;
840 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
841 vector tmp = '0 0 0';
843 PlayerScoreField field;
844 for(i = 0; i < sbt_num_fields; ++i)
846 field = sbt_field[i];
847 if(field == SP_SEPARATOR)
850 if(is_spec && field != SP_NAME && field != SP_PING) {
851 pos.x += sbt_field_size[i] + hud_fontsize.x;
854 str = Scoreboard_GetField(pl, field);
855 str = Scoreboard_FixColumnWidth(i, str);
857 pos.x += sbt_field_size[i] + hud_fontsize.x;
859 if(field == SP_NAME) {
860 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
861 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
863 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
864 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
867 tmp.x = sbt_field_size[i] + hud_fontsize.x;
868 if(sbt_field_icon0 != "")
869 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
870 if(sbt_field_icon1 != "")
871 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
872 if(sbt_field_icon2 != "")
873 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
876 if(sbt_field[i] == SP_SEPARATOR)
878 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
879 for(i = sbt_num_fields-1; i > 0; --i)
881 field = sbt_field[i];
882 if(field == SP_SEPARATOR)
885 if(is_spec && field != SP_NAME && field != SP_PING) {
886 pos.x -= sbt_field_size[i] + hud_fontsize.x;
890 str = Scoreboard_GetField(pl, field);
891 str = Scoreboard_FixColumnWidth(i, str);
893 if(field == SP_NAME) {
894 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
895 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
897 tmp.x = sbt_fixcolumnwidth_len;
898 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
901 tmp.x = sbt_field_size[i];
902 if(sbt_field_icon0 != "")
903 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
904 if(sbt_field_icon1 != "")
905 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 if(sbt_field_icon2 != "")
907 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
908 pos.x -= sbt_field_size[i] + hud_fontsize.x;
913 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
916 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
919 vector h_pos = item_pos;
920 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
922 bool complete = (this_team == NUM_SPECTATOR);
925 if((sbt_highlight) && (!(pl_number % 2)))
926 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
928 vector pos = item_pos;
929 pos.x += hud_fontsize.x * 0.5;
930 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
932 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
934 width_limit -= stringwidth("...", false, hud_fontsize);
935 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
936 static float max_name_width = 0;
939 float min_fieldsize = 0;
940 float fieldpadding = hud_fontsize.x * 0.25;
941 if(this_team == NUM_SPECTATOR)
943 if(autocvar_hud_panel_scoreboard_spectators_showping)
944 min_fieldsize = stringwidth("999", false, hud_fontsize);
946 else if(autocvar_hud_panel_scoreboard_others_showscore)
947 min_fieldsize = stringwidth("99", false, hud_fontsize);
948 for(i = 0; pl; pl = pl.sort_next)
950 if(pl.team != this_team)
956 if(this_team == NUM_SPECTATOR)
958 if(autocvar_hud_panel_scoreboard_spectators_showping)
959 field = Scoreboard_GetField(pl, SP_PING);
961 else if(autocvar_hud_panel_scoreboard_others_showscore)
962 field = Scoreboard_GetField(pl, SP_SCORE);
964 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
965 float column_width = stringwidth(str, true, hud_fontsize);
966 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
968 if(column_width > max_name_width)
969 max_name_width = column_width;
970 column_width = max_name_width;
974 fieldsize = stringwidth(field, false, hud_fontsize);
975 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
978 if(pos.x + column_width > width_limit)
983 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
988 pos.x = item_pos.x + hud_fontsize.x * 0.5;
989 pos.y += hud_fontsize.y * 1.25;
993 vector name_pos = pos;
994 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
995 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
996 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
999 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1000 h_size.y = hud_fontsize.y;
1001 vector field_pos = pos;
1002 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1003 field_pos.x += column_width - h_size.x;
1005 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1006 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1007 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1009 pos.x += column_width;
1010 pos.x += hud_fontsize.x;
1012 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1015 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1017 int max_players = 999;
1018 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1020 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1023 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1024 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1025 height /= team_count;
1028 height -= panel_bg_padding * 2; // - padding
1029 max_players = floor(height / (hud_fontsize.y * 1.25));
1030 if(max_players <= 1)
1032 if(max_players == tm.team_size)
1037 entity me = playerslots[current_player];
1039 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1040 panel_size.y += panel_bg_padding * 2;
1043 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1044 if(panel.current_panel_bg != "0")
1045 end_pos.y += panel_bg_border * 2;
1047 if(panel_bg_padding)
1049 panel_pos += '1 1 0' * panel_bg_padding;
1050 panel_size -= '2 2 0' * panel_bg_padding;
1054 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1058 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1060 pos.y += 1.25 * hud_fontsize.y;
1063 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1065 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1068 // print header row and highlight columns
1069 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1071 // fill the table and draw the rows
1072 bool is_self = false;
1073 bool self_shown = false;
1075 for(pl = players.sort_next; pl; pl = pl.sort_next)
1077 if(pl.team != tm.team)
1079 if(i == max_players - 2 && pl != me)
1081 if(!self_shown && me.team == tm.team)
1083 Scoreboard_DrawItem(pos, rgb, me, true, i);
1085 pos.y += 1.25 * hud_fontsize.y;
1089 if(i >= max_players - 1)
1091 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1094 is_self = (pl.sv_entnum == current_player);
1095 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1098 pos.y += 1.25 * hud_fontsize.y;
1102 panel_size.x += panel_bg_padding * 2; // restore initial width
1106 bool Scoreboard_WouldDraw()
1108 if (MUTATOR_CALLHOOK(DrawScoreboard))
1110 else if (QuickMenu_IsOpened())
1112 else if (HUD_Radar_Clickable())
1114 else if (scoreboard_showscores)
1116 else if (intermission == 1)
1118 else if (intermission == 2)
1120 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1122 else if (scoreboard_showscores_force)
1127 float average_accuracy;
1128 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1130 WepSet weapons_stat = WepSet_GetFromStat();
1131 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1132 int disownedcnt = 0;
1134 FOREACH(Weapons, it != WEP_Null, {
1135 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1137 WepSet set = it.m_wepset;
1138 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1140 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1147 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1148 if (weapon_cnt <= 0) return pos;
1151 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1153 int columnns = ceil(weapon_cnt / rows);
1155 float weapon_height = 29;
1156 float height = hud_fontsize.y + weapon_height;
1158 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1159 pos.y += 1.25 * hud_fontsize.y;
1160 if(panel.current_panel_bg != "0")
1161 pos.y += panel_bg_border;
1164 panel_size.y = height * rows;
1165 panel_size.y += panel_bg_padding * 2;
1168 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1169 if(panel.current_panel_bg != "0")
1170 end_pos.y += panel_bg_border * 2;
1172 if(panel_bg_padding)
1174 panel_pos += '1 1 0' * panel_bg_padding;
1175 panel_size -= '2 2 0' * panel_bg_padding;
1179 vector tmp = panel_size;
1181 float weapon_width = tmp.x / columnns / rows;
1184 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1188 // column highlighting
1189 for (int i = 0; i < columnns; ++i)
1191 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1194 for (int i = 0; i < rows; ++i)
1195 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1198 average_accuracy = 0;
1199 int weapons_with_stats = 0;
1201 pos.x += weapon_width / 2;
1203 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1206 Accuracy_LoadColors();
1208 float oldposx = pos.x;
1212 FOREACH(Weapons, it != WEP_Null, {
1213 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1215 WepSet set = it.m_wepset;
1216 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1220 if (weapon_stats >= 0)
1221 weapon_alpha = sbt_fg_alpha;
1223 weapon_alpha = 0.2 * sbt_fg_alpha;
1226 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1228 if (weapon_stats >= 0) {
1229 weapons_with_stats += 1;
1230 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1233 s = sprintf("%d%%", weapon_stats * 100);
1236 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1238 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1239 rgb = Accuracy_GetColor(weapon_stats);
1241 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1243 tmpos.x += weapon_width * rows;
1244 pos.x += weapon_width * rows;
1245 if (rows == 2 && column == columnns - 1) {
1253 if (weapons_with_stats)
1254 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1256 panel_size.x += panel_bg_padding * 2; // restore initial width
1260 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1262 pos.x += hud_fontsize.x * 0.25;
1263 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1264 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1265 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1267 pos.y += hud_fontsize.y;
1272 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1273 float stat_secrets_found, stat_secrets_total;
1274 float stat_monsters_killed, stat_monsters_total;
1278 // get monster stats
1279 stat_monsters_killed = STAT(MONSTERS_KILLED);
1280 stat_monsters_total = STAT(MONSTERS_TOTAL);
1282 // get secrets stats
1283 stat_secrets_found = STAT(SECRETS_FOUND);
1284 stat_secrets_total = STAT(SECRETS_TOTAL);
1286 // get number of rows
1287 if(stat_secrets_total)
1289 if(stat_monsters_total)
1292 // if no rows, return
1296 // draw table header
1297 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1298 pos.y += 1.25 * hud_fontsize.y;
1299 if(panel.current_panel_bg != "0")
1300 pos.y += panel_bg_border;
1303 panel_size.y = hud_fontsize.y * rows;
1304 panel_size.y += panel_bg_padding * 2;
1307 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1308 if(panel.current_panel_bg != "0")
1309 end_pos.y += panel_bg_border * 2;
1311 if(panel_bg_padding)
1313 panel_pos += '1 1 0' * panel_bg_padding;
1314 panel_size -= '2 2 0' * panel_bg_padding;
1318 vector tmp = panel_size;
1321 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1324 if(stat_monsters_total)
1326 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1327 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1331 if(stat_secrets_total)
1333 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1334 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1337 panel_size.x += panel_bg_padding * 2; // restore initial width
1342 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1345 RANKINGS_RECEIVED_CNT = 0;
1346 for (i=RANKINGS_CNT-1; i>=0; --i)
1348 ++RANKINGS_RECEIVED_CNT;
1350 if (RANKINGS_RECEIVED_CNT == 0)
1353 vector hl_rgb = rgb + '0.5 0.5 0.5';
1355 pos.y += hud_fontsize.y;
1356 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1357 pos.y += 1.25 * hud_fontsize.y;
1358 if(panel.current_panel_bg != "0")
1359 pos.y += panel_bg_border;
1364 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1366 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1371 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1373 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1377 float ranksize = 3 * hud_fontsize.x;
1378 float timesize = 5 * hud_fontsize.x;
1379 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1380 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1381 columns = min(columns, RANKINGS_RECEIVED_CNT);
1383 // expand name column to fill the entire row
1384 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1385 namesize += available_space;
1386 columnsize.x += available_space;
1388 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1389 panel_size.y += panel_bg_padding * 2;
1393 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1394 if(panel.current_panel_bg != "0")
1395 end_pos.y += panel_bg_border * 2;
1397 if(panel_bg_padding)
1399 panel_pos += '1 1 0' * panel_bg_padding;
1400 panel_size -= '2 2 0' * panel_bg_padding;
1406 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1408 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1410 int column = 0, j = 0;
1411 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1418 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1419 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1420 else if(!((j + column) & 1) && sbt_highlight)
1421 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1423 str = count_ordinal(i+1);
1424 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1425 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1426 str = ColorTranslateRGB(grecordholder[i]);
1428 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1429 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1431 pos.y += 1.25 * hud_fontsize.y;
1433 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1437 pos.x += panel_size.x / columns;
1438 pos.y = panel_pos.y;
1442 panel_size.x += panel_bg_padding * 2; // restore initial width
1446 void Scoreboard_Draw()
1448 if(!autocvar__hud_configure)
1450 if(!hud_draw_maximized) return;
1452 // frametime checks allow to toggle the scoreboard even when the game is paused
1453 if(scoreboard_active) {
1454 if(hud_configure_menu_open == 1)
1455 scoreboard_fade_alpha = 1;
1456 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1457 if (scoreboard_fadeinspeed && frametime)
1458 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1460 scoreboard_fade_alpha = 1;
1461 if(hud_fontsize_str != autocvar_hud_fontsize)
1463 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1464 Scoreboard_initFieldSizes();
1465 if(hud_fontsize_str)
1466 strunzone(hud_fontsize_str);
1467 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1471 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1472 if (scoreboard_fadeoutspeed && frametime)
1473 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1475 scoreboard_fade_alpha = 0;
1478 if (!scoreboard_fade_alpha)
1482 scoreboard_fade_alpha = 0;
1484 if (autocvar_hud_panel_scoreboard_dynamichud)
1487 HUD_Scale_Disable();
1489 if(scoreboard_fade_alpha <= 0)
1491 panel_fade_alpha *= scoreboard_fade_alpha;
1492 HUD_Panel_LoadCvars();
1494 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1495 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1496 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1497 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1498 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1499 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1501 // don't overlap with con_notify
1502 if(!autocvar__hud_configure)
1503 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1505 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1506 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1507 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1508 panel_size.x = fixed_scoreboard_width;
1510 Scoreboard_UpdatePlayerTeams();
1512 vector pos = panel_pos;
1518 vector sb_heading_fontsize;
1519 sb_heading_fontsize = hud_fontsize * 2;
1520 draw_beginBoldFont();
1521 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1524 pos.y += sb_heading_fontsize.y;
1525 if(panel.current_panel_bg != "0")
1526 pos.y += panel_bg_border;
1528 // Draw the scoreboard
1529 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1532 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1536 vector panel_bg_color_save = panel_bg_color;
1537 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1538 if(panel.current_panel_bg != "0")
1539 team_score_baseoffset.x -= panel_bg_border;
1540 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1542 if(tm.team == NUM_SPECTATOR)
1547 draw_beginBoldFont();
1548 vector rgb = Team_ColorRGB(tm.team);
1549 str = ftos(tm.(teamscores(ts_primary)));
1550 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1551 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1553 if(ts_primary != ts_secondary)
1555 str = ftos(tm.(teamscores(ts_secondary)));
1556 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1557 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1560 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1561 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1562 else if(panel_bg_color_team > 0)
1563 panel_bg_color = rgb * panel_bg_color_team;
1565 panel_bg_color = rgb;
1566 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1568 panel_bg_color = panel_bg_color_save;
1572 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1573 if(tm.team != NUM_SPECTATOR)
1575 // display it anyway
1576 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1579 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1581 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1582 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1584 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1585 if(race_speedaward) {
1586 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);
1587 pos.y += 1.25 * hud_fontsize.y;
1589 if(race_speedaward_alltimebest) {
1590 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);
1591 pos.y += 1.25 * hud_fontsize.y;
1593 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1596 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1599 for(pl = players.sort_next; pl; pl = pl.sort_next)
1601 if(pl.team == NUM_SPECTATOR)
1603 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1604 if(tm.team == NUM_SPECTATOR)
1606 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1607 draw_beginBoldFont();
1608 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1610 pos.y += 1.25 * hud_fontsize.y;
1612 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1613 pos.y += 1.25 * hud_fontsize.y;
1619 // Print info string
1621 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1622 tl = STAT(TIMELIMIT);
1623 fl = STAT(FRAGLIMIT);
1624 ll = STAT(LEADLIMIT);
1625 if(gametype == MAPINFO_TYPE_LMS)
1628 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1633 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1637 str = strcat(str, _(" or"));
1640 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1641 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1642 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1643 TranslateScoresLabel(teamscores_label(ts_primary))));
1647 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1648 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1649 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1650 TranslateScoresLabel(scores_label(ps_primary))));
1655 if(tl > 0 || fl > 0)
1656 str = strcat(str, _(" or"));
1659 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1660 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1661 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1662 TranslateScoresLabel(teamscores_label(ts_primary))));
1666 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1667 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1668 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1669 TranslateScoresLabel(scores_label(ps_primary))));
1674 pos.y += 1.2 * hud_fontsize.y;
1675 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1677 // print information about respawn status
1678 float respawn_time = STAT(RESPAWN_TIME);
1682 if(respawn_time < 0)
1684 // a negative number means we are awaiting respawn, time value is still the same
1685 respawn_time *= -1; // remove mark now that we checked it
1687 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1688 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1690 str = sprintf(_("^1Respawning in ^3%s^1..."),
1691 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1692 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1694 count_seconds(ceil(respawn_time - time))
1698 else if(time < respawn_time)
1700 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1701 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1702 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1704 count_seconds(ceil(respawn_time - time))
1708 else if(time >= respawn_time)
1709 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1711 pos.y += 1.2 * hud_fontsize.y;
1712 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1715 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;