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.\n"));
299 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
300 LOG_INFO(_("Usage:\n"));
301 LOG_INFO(_("^2scoreboard_columns_set default\n"));
302 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
303 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
304 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
307 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
308 LOG_INFO(_("^3ping^7 Ping time\n"));
309 LOG_INFO(_("^3pl^7 Packet loss\n"));
310 LOG_INFO(_("^3elo^7 Player ELO\n"));
311 LOG_INFO(_("^3kills^7 Number of kills\n"));
312 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
313 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
314 LOG_INFO(_("^3frags^7 kills - suicides\n"));
315 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
316 LOG_INFO(_("^3dmg^7 The total damage done\n"));
317 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
318 LOG_INFO(_("^3sum^7 frags - deaths\n"));
319 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
320 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
321 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
322 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
323 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
324 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
325 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
326 LOG_INFO(_("^3rank^7 Player rank\n"));
327 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
328 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
329 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
330 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
331 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
332 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
333 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
334 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
335 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
336 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
337 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
338 LOG_INFO(_("^3score^7 Total score\n"));
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\n"));
350 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
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\n"
353 "other gamemodes except DM.\n"));
356 // NOTE: adding a gametype with ? to not warn for an optional field
357 // make sure it's excluded in a previous exclusive rule, if any
358 // otherwise the previous exclusive rule warns anyway
359 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
360 #define SCOREBOARD_DEFAULT_COLUMNS \
362 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
363 " -teams,lms/deaths +ft,tdm/deaths" \
364 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
365 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
366 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
367 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
368 " +lms/lives +lms/rank" \
369 " +kh/caps +kh/pushes +kh/destroyed" \
370 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
371 " +as/objectives +nb/faults +nb/goals" \
372 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
373 " -lms,rc,cts,inv,nb/score"
375 void Cmd_Scoreboard_SetFields(int argc)
380 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
384 return; // do nothing, we don't know gametype and scores yet
386 // sbt_fields uses strunzone on the titles!
387 if(!sbt_field_title[0])
388 for(i = 0; i < MAX_SBT_FIELDS; ++i)
389 sbt_field_title[i] = strzone("(null)");
391 // TODO: re enable with gametype dependant cvars?
392 if(argc < 3) // no arguments provided
393 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
396 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
400 if(argv(2) == "default")
401 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
402 else if(argv(2) == "all")
405 s = "ping pl name |";
406 FOREACH(Scores, true, {
408 if(it != ps_secondary)
409 if(scores_label(it) != "")
410 s = strcat(s, " ", scores_label(it));
412 if(ps_secondary != ps_primary)
413 s = strcat(s, " ", scores_label(ps_secondary));
414 s = strcat(s, " ", scores_label(ps_primary));
415 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
422 hud_fontsize = HUD_GetFontsize("hud_fontsize");
424 for(i = 1; i < argc - 1; ++i)
430 if(substring(str, 0, 1) == "?")
433 str = substring(str, 1, strlen(str) - 1);
436 slash = strstrofs(str, "/", 0);
439 pattern = substring(str, 0, slash);
440 str = substring(str, slash + 1, strlen(str) - (slash + 1));
442 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
446 strunzone(sbt_field_title[sbt_num_fields]);
447 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
448 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
449 str = strtolower(str);
454 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
455 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
456 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
457 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
458 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
459 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
460 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
461 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
462 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
465 FOREACH(Scores, true, {
466 if (str == strtolower(scores_label(it))) {
468 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
478 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
482 sbt_field[sbt_num_fields] = j;
485 if(j == ps_secondary)
486 have_secondary = true;
491 if(sbt_num_fields >= MAX_SBT_FIELDS)
495 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
497 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
498 have_secondary = true;
499 if(ps_primary == ps_secondary)
500 have_secondary = true;
501 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
503 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
507 strunzone(sbt_field_title[sbt_num_fields]);
508 for(i = sbt_num_fields; i > 0; --i)
510 sbt_field_title[i] = sbt_field_title[i-1];
511 sbt_field_size[i] = sbt_field_size[i-1];
512 sbt_field[i] = sbt_field[i-1];
514 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
515 sbt_field[0] = SP_NAME;
517 LOG_INFO("fixed missing field 'name'\n");
521 strunzone(sbt_field_title[sbt_num_fields]);
522 for(i = sbt_num_fields; i > 1; --i)
524 sbt_field_title[i] = sbt_field_title[i-1];
525 sbt_field_size[i] = sbt_field_size[i-1];
526 sbt_field[i] = sbt_field[i-1];
528 sbt_field_title[1] = strzone("|");
529 sbt_field[1] = SP_SEPARATOR;
530 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
532 LOG_INFO("fixed missing field '|'\n");
535 else if(!have_separator)
537 strunzone(sbt_field_title[sbt_num_fields]);
538 sbt_field_title[sbt_num_fields] = strzone("|");
539 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
540 sbt_field[sbt_num_fields] = SP_SEPARATOR;
542 LOG_INFO("fixed missing field '|'\n");
546 strunzone(sbt_field_title[sbt_num_fields]);
547 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
548 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
549 sbt_field[sbt_num_fields] = ps_secondary;
551 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
555 strunzone(sbt_field_title[sbt_num_fields]);
556 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
557 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
558 sbt_field[sbt_num_fields] = ps_primary;
560 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
564 sbt_field[sbt_num_fields] = SP_END;
568 vector sbt_field_rgb;
569 string sbt_field_icon0;
570 string sbt_field_icon1;
571 string sbt_field_icon2;
572 vector sbt_field_icon0_rgb;
573 vector sbt_field_icon1_rgb;
574 vector sbt_field_icon2_rgb;
575 string Scoreboard_GetName(entity pl)
577 if(ready_waiting && pl.ready)
579 sbt_field_icon0 = "gfx/scoreboard/player_ready";
583 int f = entcs_GetClientColors(pl.sv_entnum);
585 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
586 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
587 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
588 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
589 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
592 return entcs_GetName(pl.sv_entnum);
594 string Scoreboard_GetField(entity pl, PlayerScoreField field)
596 float tmp, num, denom;
599 sbt_field_rgb = '1 1 1';
600 sbt_field_icon0 = "";
601 sbt_field_icon1 = "";
602 sbt_field_icon2 = "";
603 sbt_field_icon0_rgb = '1 1 1';
604 sbt_field_icon1_rgb = '1 1 1';
605 sbt_field_icon2_rgb = '1 1 1';
610 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
611 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
615 tmp = max(0, min(220, f-80)) / 220;
616 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
622 f = pl.ping_packetloss;
623 tmp = pl.ping_movementloss;
624 if(f == 0 && tmp == 0)
626 str = ftos(ceil(f * 100));
628 str = strcat(str, "~", ftos(ceil(tmp * 100)));
629 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
630 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
634 return Scoreboard_GetName(pl);
637 f = pl.(scores(SP_KILLS));
638 f -= pl.(scores(SP_SUICIDES));
642 num = pl.(scores(SP_KILLS));
643 denom = pl.(scores(SP_DEATHS));
646 sbt_field_rgb = '0 1 0';
647 str = sprintf("%d", num);
648 } else if(num <= 0) {
649 sbt_field_rgb = '1 0 0';
650 str = sprintf("%.1f", num/denom);
652 str = sprintf("%.1f", num/denom);
656 f = pl.(scores(SP_KILLS));
657 f -= pl.(scores(SP_DEATHS));
660 sbt_field_rgb = '0 1 0';
662 sbt_field_rgb = '1 1 1';
664 sbt_field_rgb = '1 0 0';
670 float elo = pl.(scores(SP_ELO));
672 case -1: return "...";
673 case -2: return _("N/A");
674 default: return ftos(elo);
678 case SP_DMG: case SP_DMGTAKEN:
679 return sprintf("%.1f k", pl.(scores(field)) / 1000);
681 default: case SP_SCORE:
682 tmp = pl.(scores(field));
683 f = scores_flags(field);
684 if(field == ps_primary)
685 sbt_field_rgb = '1 1 0';
686 else if(field == ps_secondary)
687 sbt_field_rgb = '0 1 1';
689 sbt_field_rgb = '1 1 1';
690 return ScoreString(f, tmp);
695 float sbt_fixcolumnwidth_len;
696 float sbt_fixcolumnwidth_iconlen;
697 float sbt_fixcolumnwidth_marginlen;
699 string Scoreboard_FixColumnWidth(int i, string str)
705 sbt_fixcolumnwidth_iconlen = 0;
707 if(sbt_field_icon0 != "")
709 sz = draw_getimagesize(sbt_field_icon0);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 if(sbt_field_icon1 != "")
717 sz = draw_getimagesize(sbt_field_icon1);
719 if(sbt_fixcolumnwidth_iconlen < f)
720 sbt_fixcolumnwidth_iconlen = f;
723 if(sbt_field_icon2 != "")
725 sz = draw_getimagesize(sbt_field_icon2);
727 if(sbt_fixcolumnwidth_iconlen < f)
728 sbt_fixcolumnwidth_iconlen = f;
731 if(sbt_fixcolumnwidth_iconlen != 0)
733 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
734 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
737 sbt_fixcolumnwidth_marginlen = 0;
739 if(sbt_field[i] == SP_NAME) // name gets all remaining space
742 float remaining_space = 0;
743 for(j = 0; j < sbt_num_fields; ++j)
745 if (sbt_field[i] != SP_SEPARATOR)
746 remaining_space += sbt_field_size[j] + hud_fontsize.x;
747 sbt_field_size[i] = panel_size.x - remaining_space;
749 if (sbt_fixcolumnwidth_iconlen != 0)
750 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 float namesize = panel_size.x - remaining_space;
752 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
753 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
755 max_namesize = vid_conwidth - remaining_space;
758 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
760 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
761 if(sbt_field_size[i] < f)
762 sbt_field_size[i] = f;
767 void Scoreboard_initFieldSizes()
769 for(int i = 0; i < sbt_num_fields; ++i)
771 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
772 Scoreboard_FixColumnWidth(i, "");
776 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
779 vector column_dim = eY * panel_size.y;
781 column_dim.y -= 1.25 * hud_fontsize.y;
782 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
783 pos.x += hud_fontsize.x * 0.5;
784 for(i = 0; i < sbt_num_fields; ++i)
786 if(sbt_field[i] == SP_SEPARATOR)
788 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
791 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
792 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
793 pos.x += column_dim.x;
795 if(sbt_field[i] == SP_SEPARATOR)
797 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
798 for(i = sbt_num_fields - 1; i > 0; --i)
800 if(sbt_field[i] == SP_SEPARATOR)
803 pos.x -= sbt_field_size[i];
808 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
809 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
812 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
813 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
814 pos.x -= hud_fontsize.x;
819 pos.y += 1.25 * hud_fontsize.y;
823 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
825 TC(bool, is_self); TC(int, pl_number);
827 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
829 vector h_pos = item_pos;
830 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
831 // alternated rows highlighting
833 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
834 else if((sbt_highlight) && (!(pl_number % 2)))
835 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
837 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
839 vector pos = item_pos;
840 pos.x += hud_fontsize.x * 0.5;
841 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
842 vector tmp = '0 0 0';
844 PlayerScoreField field;
845 for(i = 0; i < sbt_num_fields; ++i)
847 field = sbt_field[i];
848 if(field == SP_SEPARATOR)
851 if(is_spec && field != SP_NAME && field != SP_PING) {
852 pos.x += sbt_field_size[i] + hud_fontsize.x;
855 str = Scoreboard_GetField(pl, field);
856 str = Scoreboard_FixColumnWidth(i, str);
858 pos.x += sbt_field_size[i] + hud_fontsize.x;
860 if(field == SP_NAME) {
861 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
862 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
864 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
865 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
868 tmp.x = sbt_field_size[i] + hud_fontsize.x;
869 if(sbt_field_icon0 != "")
870 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
871 if(sbt_field_icon1 != "")
872 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
873 if(sbt_field_icon2 != "")
874 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field[i] == SP_SEPARATOR)
879 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
880 for(i = sbt_num_fields-1; i > 0; --i)
882 field = sbt_field[i];
883 if(field == SP_SEPARATOR)
886 if(is_spec && field != SP_NAME && field != SP_PING) {
887 pos.x -= sbt_field_size[i] + hud_fontsize.x;
891 str = Scoreboard_GetField(pl, field);
892 str = Scoreboard_FixColumnWidth(i, str);
894 if(field == SP_NAME) {
895 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
896 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_fixcolumnwidth_len;
899 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_field_size[i];
903 if(sbt_field_icon0 != "")
904 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
905 if(sbt_field_icon1 != "")
906 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
907 if(sbt_field_icon2 != "")
908 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
909 pos.x -= sbt_field_size[i] + hud_fontsize.x;
914 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
917 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
920 vector h_pos = item_pos;
921 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
923 bool complete = (this_team == NUM_SPECTATOR);
926 if((sbt_highlight) && (!(pl_number % 2)))
927 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
929 vector pos = item_pos;
930 pos.x += hud_fontsize.x * 0.5;
931 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
933 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
935 width_limit -= stringwidth("...", false, hud_fontsize);
936 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
937 static float max_name_width = 0;
940 float min_fieldsize = 0;
941 float fieldpadding = hud_fontsize.x * 0.25;
942 if(this_team == NUM_SPECTATOR)
944 if(autocvar_hud_panel_scoreboard_spectators_showping)
945 min_fieldsize = stringwidth("999", false, hud_fontsize);
947 else if(autocvar_hud_panel_scoreboard_others_showscore)
948 min_fieldsize = stringwidth("99", false, hud_fontsize);
949 for(i = 0; pl; pl = pl.sort_next)
951 if(pl.team != this_team)
957 if(this_team == NUM_SPECTATOR)
959 if(autocvar_hud_panel_scoreboard_spectators_showping)
960 field = Scoreboard_GetField(pl, SP_PING);
962 else if(autocvar_hud_panel_scoreboard_others_showscore)
963 field = Scoreboard_GetField(pl, SP_SCORE);
965 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
966 float column_width = stringwidth(str, true, hud_fontsize);
967 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
969 if(column_width > max_name_width)
970 max_name_width = column_width;
971 column_width = max_name_width;
975 fieldsize = stringwidth(field, false, hud_fontsize);
976 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
979 if(pos.x + column_width > width_limit)
984 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
989 pos.x = item_pos.x + hud_fontsize.x * 0.5;
990 pos.y += hud_fontsize.y * 1.25;
994 vector name_pos = pos;
995 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
996 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
997 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1000 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1001 h_size.y = hud_fontsize.y;
1002 vector field_pos = pos;
1003 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1004 field_pos.x += column_width - h_size.x;
1006 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1007 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1008 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1010 pos.x += column_width;
1011 pos.x += hud_fontsize.x;
1013 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1016 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1018 int max_players = 999;
1019 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1021 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1024 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1025 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1026 height /= team_count;
1029 height -= panel_bg_padding * 2; // - padding
1030 max_players = floor(height / (hud_fontsize.y * 1.25));
1031 if(max_players <= 1)
1033 if(max_players == tm.team_size)
1038 entity me = playerslots[current_player];
1040 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1041 panel_size.y += panel_bg_padding * 2;
1044 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1045 if(panel.current_panel_bg != "0")
1046 end_pos.y += panel_bg_border * 2;
1048 if(panel_bg_padding)
1050 panel_pos += '1 1 0' * panel_bg_padding;
1051 panel_size -= '2 2 0' * panel_bg_padding;
1055 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1059 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1061 pos.y += 1.25 * hud_fontsize.y;
1064 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1066 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1069 // print header row and highlight columns
1070 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1072 // fill the table and draw the rows
1073 bool is_self = false;
1074 bool self_shown = false;
1076 for(pl = players.sort_next; pl; pl = pl.sort_next)
1078 if(pl.team != tm.team)
1080 if(i == max_players - 2 && pl != me)
1082 if(!self_shown && me.team == tm.team)
1084 Scoreboard_DrawItem(pos, rgb, me, true, i);
1086 pos.y += 1.25 * hud_fontsize.y;
1090 if(i >= max_players - 1)
1092 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1095 is_self = (pl.sv_entnum == current_player);
1096 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1099 pos.y += 1.25 * hud_fontsize.y;
1103 panel_size.x += panel_bg_padding * 2; // restore initial width
1107 bool Scoreboard_WouldDraw()
1109 if (MUTATOR_CALLHOOK(DrawScoreboard))
1111 else if (QuickMenu_IsOpened())
1113 else if (HUD_Radar_Clickable())
1115 else if (scoreboard_showscores)
1117 else if (intermission == 1)
1119 else if (intermission == 2)
1121 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1123 else if (scoreboard_showscores_force)
1128 float average_accuracy;
1129 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1131 WepSet weapons_stat = WepSet_GetFromStat();
1132 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1133 int disownedcnt = 0;
1135 FOREACH(Weapons, it != WEP_Null, {
1136 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1138 WepSet set = it.m_wepset;
1139 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1141 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1148 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1149 if (weapon_cnt <= 0) return pos;
1152 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1154 int columnns = ceil(weapon_cnt / rows);
1156 float weapon_height = 29;
1157 float height = hud_fontsize.y + weapon_height;
1159 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1160 pos.y += 1.25 * hud_fontsize.y;
1161 if(panel.current_panel_bg != "0")
1162 pos.y += panel_bg_border;
1165 panel_size.y = height * rows;
1166 panel_size.y += panel_bg_padding * 2;
1169 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1170 if(panel.current_panel_bg != "0")
1171 end_pos.y += panel_bg_border * 2;
1173 if(panel_bg_padding)
1175 panel_pos += '1 1 0' * panel_bg_padding;
1176 panel_size -= '2 2 0' * panel_bg_padding;
1180 vector tmp = panel_size;
1182 float weapon_width = tmp.x / columnns / rows;
1185 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1189 // column highlighting
1190 for (int i = 0; i < columnns; ++i)
1192 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1195 for (int i = 0; i < rows; ++i)
1196 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1199 average_accuracy = 0;
1200 int weapons_with_stats = 0;
1202 pos.x += weapon_width / 2;
1204 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1207 Accuracy_LoadColors();
1209 float oldposx = pos.x;
1213 FOREACH(Weapons, it != WEP_Null, {
1214 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1216 WepSet set = it.m_wepset;
1217 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1221 if (weapon_stats >= 0)
1222 weapon_alpha = sbt_fg_alpha;
1224 weapon_alpha = 0.2 * sbt_fg_alpha;
1227 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1229 if (weapon_stats >= 0) {
1230 weapons_with_stats += 1;
1231 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1234 s = sprintf("%d%%", weapon_stats * 100);
1237 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1239 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1240 rgb = Accuracy_GetColor(weapon_stats);
1242 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 tmpos.x += weapon_width * rows;
1245 pos.x += weapon_width * rows;
1246 if (rows == 2 && column == columnns - 1) {
1254 if (weapons_with_stats)
1255 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1257 panel_size.x += panel_bg_padding * 2; // restore initial width
1261 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1263 pos.x += hud_fontsize.x * 0.25;
1264 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1265 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1266 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1268 pos.y += hud_fontsize.y;
1273 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1274 float stat_secrets_found, stat_secrets_total;
1275 float stat_monsters_killed, stat_monsters_total;
1279 // get monster stats
1280 stat_monsters_killed = STAT(MONSTERS_KILLED);
1281 stat_monsters_total = STAT(MONSTERS_TOTAL);
1283 // get secrets stats
1284 stat_secrets_found = STAT(SECRETS_FOUND);
1285 stat_secrets_total = STAT(SECRETS_TOTAL);
1287 // get number of rows
1288 if(stat_secrets_total)
1290 if(stat_monsters_total)
1293 // if no rows, return
1297 // draw table header
1298 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1299 pos.y += 1.25 * hud_fontsize.y;
1300 if(panel.current_panel_bg != "0")
1301 pos.y += panel_bg_border;
1304 panel_size.y = hud_fontsize.y * rows;
1305 panel_size.y += panel_bg_padding * 2;
1308 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1309 if(panel.current_panel_bg != "0")
1310 end_pos.y += panel_bg_border * 2;
1312 if(panel_bg_padding)
1314 panel_pos += '1 1 0' * panel_bg_padding;
1315 panel_size -= '2 2 0' * panel_bg_padding;
1319 vector tmp = panel_size;
1322 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1325 if(stat_monsters_total)
1327 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1328 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1332 if(stat_secrets_total)
1334 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1335 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1338 panel_size.x += panel_bg_padding * 2; // restore initial width
1343 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1346 RANKINGS_RECEIVED_CNT = 0;
1347 for (i=RANKINGS_CNT-1; i>=0; --i)
1349 ++RANKINGS_RECEIVED_CNT;
1351 if (RANKINGS_RECEIVED_CNT == 0)
1354 vector hl_rgb = rgb + '0.5 0.5 0.5';
1356 pos.y += hud_fontsize.y;
1357 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1358 pos.y += 1.25 * hud_fontsize.y;
1359 if(panel.current_panel_bg != "0")
1360 pos.y += panel_bg_border;
1365 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1367 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1372 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1374 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1378 float ranksize = 3 * hud_fontsize.x;
1379 float timesize = 5 * hud_fontsize.x;
1380 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1381 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1382 columns = min(columns, RANKINGS_RECEIVED_CNT);
1384 // expand name column to fill the entire row
1385 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1386 namesize += available_space;
1387 columnsize.x += available_space;
1389 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1390 panel_size.y += panel_bg_padding * 2;
1394 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1395 if(panel.current_panel_bg != "0")
1396 end_pos.y += panel_bg_border * 2;
1398 if(panel_bg_padding)
1400 panel_pos += '1 1 0' * panel_bg_padding;
1401 panel_size -= '2 2 0' * panel_bg_padding;
1407 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1409 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1411 int column = 0, j = 0;
1412 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1419 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1420 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1421 else if(!((j + column) & 1) && sbt_highlight)
1422 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1424 str = count_ordinal(i+1);
1425 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1426 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1427 str = ColorTranslateRGB(grecordholder[i]);
1429 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1430 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1432 pos.y += 1.25 * hud_fontsize.y;
1434 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1438 pos.x += panel_size.x / columns;
1439 pos.y = panel_pos.y;
1443 panel_size.x += panel_bg_padding * 2; // restore initial width
1447 void Scoreboard_Draw()
1449 if(!autocvar__hud_configure)
1451 if(!hud_draw_maximized) return;
1453 // frametime checks allow to toggle the scoreboard even when the game is paused
1454 if(scoreboard_active) {
1455 if(hud_configure_menu_open == 1)
1456 scoreboard_fade_alpha = 1;
1457 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1458 if (scoreboard_fadeinspeed && frametime)
1459 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1461 scoreboard_fade_alpha = 1;
1462 if(hud_fontsize_str != autocvar_hud_fontsize)
1464 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1465 Scoreboard_initFieldSizes();
1466 if(hud_fontsize_str)
1467 strunzone(hud_fontsize_str);
1468 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1472 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1473 if (scoreboard_fadeoutspeed && frametime)
1474 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1476 scoreboard_fade_alpha = 0;
1479 if (!scoreboard_fade_alpha)
1483 scoreboard_fade_alpha = 0;
1485 if (autocvar_hud_panel_scoreboard_dynamichud)
1488 HUD_Scale_Disable();
1490 if(scoreboard_fade_alpha <= 0)
1492 panel_fade_alpha *= scoreboard_fade_alpha;
1493 HUD_Panel_LoadCvars();
1495 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1496 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1497 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1498 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1499 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1500 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1502 // don't overlap with con_notify
1503 if(!autocvar__hud_configure)
1504 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1506 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1507 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1508 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1509 panel_size.x = fixed_scoreboard_width;
1511 Scoreboard_UpdatePlayerTeams();
1513 vector pos = panel_pos;
1519 vector sb_heading_fontsize;
1520 sb_heading_fontsize = hud_fontsize * 2;
1521 draw_beginBoldFont();
1522 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1525 pos.y += sb_heading_fontsize.y;
1526 if(panel.current_panel_bg != "0")
1527 pos.y += panel_bg_border;
1529 // Draw the scoreboard
1530 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1533 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1537 vector panel_bg_color_save = panel_bg_color;
1538 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1539 if(panel.current_panel_bg != "0")
1540 team_score_baseoffset.x -= panel_bg_border;
1541 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1543 if(tm.team == NUM_SPECTATOR)
1548 draw_beginBoldFont();
1549 vector rgb = Team_ColorRGB(tm.team);
1550 str = ftos(tm.(teamscores(ts_primary)));
1551 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1552 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1554 if(ts_primary != ts_secondary)
1556 str = ftos(tm.(teamscores(ts_secondary)));
1557 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1558 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1561 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1562 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1563 else if(panel_bg_color_team > 0)
1564 panel_bg_color = rgb * panel_bg_color_team;
1566 panel_bg_color = rgb;
1567 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1569 panel_bg_color = panel_bg_color_save;
1573 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1574 if(tm.team != NUM_SPECTATOR)
1576 // display it anyway
1577 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1580 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1582 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1583 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1585 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1586 if(race_speedaward) {
1587 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);
1588 pos.y += 1.25 * hud_fontsize.y;
1590 if(race_speedaward_alltimebest) {
1591 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);
1592 pos.y += 1.25 * hud_fontsize.y;
1594 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1597 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1600 for(pl = players.sort_next; pl; pl = pl.sort_next)
1602 if(pl.team == NUM_SPECTATOR)
1604 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1605 if(tm.team == NUM_SPECTATOR)
1607 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1608 draw_beginBoldFont();
1609 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1611 pos.y += 1.25 * hud_fontsize.y;
1613 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1614 pos.y += 1.25 * hud_fontsize.y;
1620 // Print info string
1622 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1623 tl = STAT(TIMELIMIT);
1624 fl = STAT(FRAGLIMIT);
1625 ll = STAT(LEADLIMIT);
1626 if(gametype == MAPINFO_TYPE_LMS)
1629 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1634 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1638 str = strcat(str, _(" or"));
1641 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1642 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1643 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1644 TranslateScoresLabel(teamscores_label(ts_primary))));
1648 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1649 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1650 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1651 TranslateScoresLabel(scores_label(ps_primary))));
1656 if(tl > 0 || fl > 0)
1657 str = strcat(str, _(" or"));
1660 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1661 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1662 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1663 TranslateScoresLabel(teamscores_label(ts_primary))));
1667 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1668 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1669 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1670 TranslateScoresLabel(scores_label(ps_primary))));
1675 pos.y += 1.2 * hud_fontsize.y;
1676 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1678 // print information about respawn status
1679 float respawn_time = STAT(RESPAWN_TIME);
1683 if(respawn_time < 0)
1685 // a negative number means we are awaiting respawn, time value is still the same
1686 respawn_time *= -1; // remove mark now that we checked it
1688 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1689 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1691 str = sprintf(_("^1Respawning in ^3%s^1..."),
1692 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1693 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1695 count_seconds(ceil(respawn_time - time))
1699 else if(time < respawn_time)
1701 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1702 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1703 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1705 count_seconds(ceil(respawn_time - time))
1709 else if(time >= respawn_time)
1710 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1712 pos.y += 1.2 * hud_fontsize.y;
1713 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1716 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;