1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/net_linked.qh>
7 #include <common/mapinfo.qh>
8 #include <common/minigames/cl_minigames.qh>
9 #include <common/stats.qh>
10 #include <common/teams.qh>
14 const int MAX_SBT_FIELDS = MAX_SCORE;
16 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
17 float sbt_field_size[MAX_SBT_FIELDS + 1];
18 string sbt_field_title[MAX_SBT_FIELDS + 1];
21 string autocvar_hud_fontsize;
22 string hud_fontsize_str;
27 float sbt_fg_alpha_self;
29 float sbt_highlight_alpha;
30 float sbt_highlight_alpha_self;
32 // provide basic panel cvars to old clients
33 // TODO remove them after a future release (0.8.2+)
34 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
35 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
36 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
37 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
38 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
39 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
40 noref string autocvar_hud_panel_scoreboard_bg_border = "";
41 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
43 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
44 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
45 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
46 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
47 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
49 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
50 bool autocvar_hud_panel_scoreboard_table_highlight = true;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
52 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
53 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
54 float autocvar_hud_panel_scoreboard_namesize = 15;
56 bool autocvar_hud_panel_scoreboard_accuracy = true;
57 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
58 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
61 bool autocvar_hud_panel_scoreboard_dynamichud = false;
63 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
64 bool autocvar_hud_panel_scoreboard_others_showscore = true;
65 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
66 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
67 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
70 void drawstringright(vector, string, vector, vector, float, float);
71 void drawstringcenter(vector, string, vector, vector, float, float);
73 // wrapper to put all possible scores titles through gettext
74 string TranslateScoresLabel(string l)
78 case "bckills": return CTX(_("SCO^bckills"));
79 case "bctime": return CTX(_("SCO^bctime"));
80 case "caps": return CTX(_("SCO^caps"));
81 case "captime": return CTX(_("SCO^captime"));
82 case "deaths": return CTX(_("SCO^deaths"));
83 case "destroyed": return CTX(_("SCO^destroyed"));
84 case "dmg": return CTX(_("SCO^damage"));
85 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
86 case "drops": return CTX(_("SCO^drops"));
87 case "faults": return CTX(_("SCO^faults"));
88 case "fckills": return CTX(_("SCO^fckills"));
89 case "goals": return CTX(_("SCO^goals"));
90 case "kckills": return CTX(_("SCO^kckills"));
91 case "kdratio": return CTX(_("SCO^kdratio"));
92 case "kd": return CTX(_("SCO^k/d"));
93 case "kdr": return CTX(_("SCO^kdr"));
94 case "kills": return CTX(_("SCO^kills"));
95 case "laps": return CTX(_("SCO^laps"));
96 case "lives": return CTX(_("SCO^lives"));
97 case "losses": return CTX(_("SCO^losses"));
98 case "name": return CTX(_("SCO^name"));
99 case "sum": return CTX(_("SCO^sum"));
100 case "nick": return CTX(_("SCO^nick"));
101 case "objectives": return CTX(_("SCO^objectives"));
102 case "pickups": return CTX(_("SCO^pickups"));
103 case "ping": return CTX(_("SCO^ping"));
104 case "pl": return CTX(_("SCO^pl"));
105 case "pushes": return CTX(_("SCO^pushes"));
106 case "rank": return CTX(_("SCO^rank"));
107 case "returns": return CTX(_("SCO^returns"));
108 case "revivals": return CTX(_("SCO^revivals"));
109 case "rounds": return CTX(_("SCO^rounds won"));
110 case "score": return CTX(_("SCO^score"));
111 case "suicides": return CTX(_("SCO^suicides"));
112 case "takes": return CTX(_("SCO^takes"));
113 case "ticks": return CTX(_("SCO^ticks"));
118 void Scoreboard_InitScores()
122 ps_primary = ps_secondary = NULL;
123 ts_primary = ts_secondary = -1;
124 FOREACH(Scores, true, {
125 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
126 if(f == SFL_SORT_PRIO_PRIMARY)
128 if(f == SFL_SORT_PRIO_SECONDARY)
131 if(ps_secondary == NULL)
132 ps_secondary = ps_primary;
134 for(i = 0; i < MAX_TEAMSCORE; ++i)
136 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
137 if(f == SFL_SORT_PRIO_PRIMARY)
139 if(f == SFL_SORT_PRIO_SECONDARY)
142 if(ts_secondary == -1)
143 ts_secondary = ts_primary;
145 Cmd_Scoreboard_SetFields(0);
148 float SetTeam(entity pl, float Team);
150 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
296 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
297 LOG_INFO(_("Usage:\n"));
298 LOG_INFO(_("^2scoreboard_columns_set default\n"));
299 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
300 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
301 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
304 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
305 LOG_INFO(_("^3ping^7 Ping time\n"));
306 LOG_INFO(_("^3pl^7 Packet loss\n"));
307 LOG_INFO(_("^3elo^7 Player ELO\n"));
308 LOG_INFO(_("^3kills^7 Number of kills\n"));
309 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
310 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
311 LOG_INFO(_("^3frags^7 kills - suicides\n"));
312 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
313 LOG_INFO(_("^3dmg^7 The total damage done\n"));
314 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
315 LOG_INFO(_("^3sum^7 frags - deaths\n"));
316 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
317 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
318 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
319 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
320 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
321 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
322 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
323 LOG_INFO(_("^3rank^7 Player rank\n"));
324 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
325 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
326 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
327 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
328 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
329 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
330 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
331 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
332 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
333 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
334 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
335 LOG_INFO(_("^3score^7 Total score\n"));
338 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
339 "of game types, then a slash, to make the field show up only in these\n"
340 "or in all but these game types. You can also specify 'all' as a\n"
341 "field to show all fields available for the current game mode.\n\n"));
343 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
344 "include/exclude ALL teams/noteams game modes.\n\n"));
346 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
347 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
348 "right of the vertical bar aligned to the right.\n"));
349 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
350 "other gamemodes except DM.\n"));
353 // NOTE: adding a gametype with ? to not warn for an optional field
354 // make sure it's excluded in a previous exclusive rule, if any
355 // otherwise the previous exclusive rule warns anyway
356 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
357 #define SCOREBOARD_DEFAULT_COLUMNS \
359 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
360 " -teams,lms/deaths +ft,tdm/deaths" \
361 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
362 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
363 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
364 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
365 " +lms/lives +lms/rank" \
366 " +kh/caps +kh/pushes +kh/destroyed" \
367 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
368 " +as/objectives +nb/faults +nb/goals" \
369 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
370 " -lms,rc,cts,inv,nb/score"
372 void Cmd_Scoreboard_SetFields(int argc)
377 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
381 return; // do nothing, we don't know gametype and scores yet
383 // sbt_fields uses strunzone on the titles!
384 if(!sbt_field_title[0])
385 for(i = 0; i < MAX_SBT_FIELDS; ++i)
386 sbt_field_title[i] = strzone("(null)");
388 // TODO: re enable with gametype dependant cvars?
389 if(argc < 3) // no arguments provided
390 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
393 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 if(argv(2) == "default")
398 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
399 else if(argv(2) == "all")
402 s = "ping pl name |";
403 FOREACH(Scores, true, {
405 if(it != ps_secondary)
406 if(scores_label(it) != "")
407 s = strcat(s, " ", scores_label(it));
409 if(ps_secondary != ps_primary)
410 s = strcat(s, " ", scores_label(ps_secondary));
411 s = strcat(s, " ", scores_label(ps_primary));
412 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
419 hud_fontsize = HUD_GetFontsize("hud_fontsize");
421 for(i = 1; i < argc - 1; ++i)
427 if(substring(str, 0, 1) == "?")
430 str = substring(str, 1, strlen(str) - 1);
433 slash = strstrofs(str, "/", 0);
436 pattern = substring(str, 0, slash);
437 str = substring(str, slash + 1, strlen(str) - (slash + 1));
439 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
443 strunzone(sbt_field_title[sbt_num_fields]);
444 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
445 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
446 str = strtolower(str);
451 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
452 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
453 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
454 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
455 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
456 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
457 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
458 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
459 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
462 FOREACH(Scores, true, {
463 if (str == strtolower(scores_label(it))) {
465 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
475 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
479 sbt_field[sbt_num_fields] = j;
482 if(j == ps_secondary)
483 have_secondary = true;
488 if(sbt_num_fields >= MAX_SBT_FIELDS)
492 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
494 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
495 have_secondary = true;
496 if(ps_primary == ps_secondary)
497 have_secondary = true;
498 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
500 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
504 strunzone(sbt_field_title[sbt_num_fields]);
505 for(i = sbt_num_fields; i > 0; --i)
507 sbt_field_title[i] = sbt_field_title[i-1];
508 sbt_field_size[i] = sbt_field_size[i-1];
509 sbt_field[i] = sbt_field[i-1];
511 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
512 sbt_field[0] = SP_NAME;
514 LOG_INFO("fixed missing field 'name'\n");
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 1; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[1] = strzone("|");
526 sbt_field[1] = SP_SEPARATOR;
527 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
529 LOG_INFO("fixed missing field '|'\n");
532 else if(!have_separator)
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone("|");
536 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
537 sbt_field[sbt_num_fields] = SP_SEPARATOR;
539 LOG_INFO("fixed missing field '|'\n");
543 strunzone(sbt_field_title[sbt_num_fields]);
544 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
545 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
546 sbt_field[sbt_num_fields] = ps_secondary;
548 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
552 strunzone(sbt_field_title[sbt_num_fields]);
553 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_primary;
557 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
561 sbt_field[sbt_num_fields] = SP_END;
565 vector sbt_field_rgb;
566 string sbt_field_icon0;
567 string sbt_field_icon1;
568 string sbt_field_icon2;
569 vector sbt_field_icon0_rgb;
570 vector sbt_field_icon1_rgb;
571 vector sbt_field_icon2_rgb;
572 string Scoreboard_GetField(entity pl, PlayerScoreField field)
574 float tmp, num, denom;
577 sbt_field_rgb = '1 1 1';
578 sbt_field_icon0 = "";
579 sbt_field_icon1 = "";
580 sbt_field_icon2 = "";
581 sbt_field_icon0_rgb = '1 1 1';
582 sbt_field_icon1_rgb = '1 1 1';
583 sbt_field_icon2_rgb = '1 1 1';
588 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
589 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
593 tmp = max(0, min(220, f-80)) / 220;
594 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
600 f = pl.ping_packetloss;
601 tmp = pl.ping_movementloss;
602 if(f == 0 && tmp == 0)
604 str = ftos(ceil(f * 100));
606 str = strcat(str, "~", ftos(ceil(tmp * 100)));
607 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
608 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
612 if(ready_waiting && pl.ready)
614 sbt_field_icon0 = "gfx/scoreboard/player_ready";
618 f = entcs_GetClientColors(pl.sv_entnum);
620 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
621 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
622 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
623 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
624 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
627 return entcs_GetName(pl.sv_entnum);
630 f = pl.(scores(SP_KILLS));
631 f -= pl.(scores(SP_SUICIDES));
635 num = pl.(scores(SP_KILLS));
636 denom = pl.(scores(SP_DEATHS));
639 sbt_field_rgb = '0 1 0';
640 str = sprintf("%d", num);
641 } else if(num <= 0) {
642 sbt_field_rgb = '1 0 0';
643 str = sprintf("%.1f", num/denom);
645 str = sprintf("%.1f", num/denom);
649 f = pl.(scores(SP_KILLS));
650 f -= pl.(scores(SP_DEATHS));
653 sbt_field_rgb = '0 1 0';
655 sbt_field_rgb = '1 1 1';
657 sbt_field_rgb = '1 0 0';
663 float elo = pl.(scores(SP_ELO));
665 case -1: return "...";
666 case -2: return _("N/A");
667 default: return ftos(elo);
671 case SP_DMG: case SP_DMGTAKEN:
672 return sprintf("%.1f k", pl.(scores(field)) / 1000);
674 default: case SP_SCORE:
675 tmp = pl.(scores(field));
676 f = scores_flags(field);
677 if(field == ps_primary)
678 sbt_field_rgb = '1 1 0';
679 else if(field == ps_secondary)
680 sbt_field_rgb = '0 1 1';
682 sbt_field_rgb = '1 1 1';
683 return ScoreString(f, tmp);
688 float sbt_fixcolumnwidth_len;
689 float sbt_fixcolumnwidth_iconlen;
690 float sbt_fixcolumnwidth_marginlen;
692 string Scoreboard_FixColumnWidth(int i, string str)
698 sbt_fixcolumnwidth_iconlen = 0;
700 if(sbt_field_icon0 != "")
702 sz = draw_getimagesize(sbt_field_icon0);
704 if(sbt_fixcolumnwidth_iconlen < f)
705 sbt_fixcolumnwidth_iconlen = f;
708 if(sbt_field_icon1 != "")
710 sz = draw_getimagesize(sbt_field_icon1);
712 if(sbt_fixcolumnwidth_iconlen < f)
713 sbt_fixcolumnwidth_iconlen = f;
716 if(sbt_field_icon2 != "")
718 sz = draw_getimagesize(sbt_field_icon2);
720 if(sbt_fixcolumnwidth_iconlen < f)
721 sbt_fixcolumnwidth_iconlen = f;
724 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
726 if(sbt_fixcolumnwidth_iconlen != 0)
727 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
729 sbt_fixcolumnwidth_marginlen = 0;
731 if(sbt_field[i] == SP_NAME) // name gets all remaining space
734 float remaining_space = 0;
735 for(j = 0; j < sbt_num_fields; ++j)
737 if (sbt_field[i] != SP_SEPARATOR)
738 remaining_space += sbt_field_size[j] + hud_fontsize.x;
739 sbt_field_size[i] = panel_size.x - remaining_space;
741 if (sbt_fixcolumnwidth_iconlen != 0)
742 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
743 float namesize = panel_size.x - remaining_space;
744 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
745 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
747 max_namesize = vid_conwidth - remaining_space;
750 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
752 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 if(sbt_field_size[i] < f)
754 sbt_field_size[i] = f;
759 void Scoreboard_initFieldSizes()
761 for(int i = 0; i < sbt_num_fields; ++i)
763 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
764 Scoreboard_FixColumnWidth(i, "");
768 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
771 vector column_dim = eY * panel_size.y;
773 column_dim.y -= 1.25 * hud_fontsize.y;
774 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
775 pos.x += hud_fontsize.x * 0.5;
776 for(i = 0; i < sbt_num_fields; ++i)
778 if(sbt_field[i] == SP_SEPARATOR)
780 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
783 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
784 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
785 pos.x += column_dim.x;
787 if(sbt_field[i] == SP_SEPARATOR)
789 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
790 for(i = sbt_num_fields - 1; i > 0; --i)
792 if(sbt_field[i] == SP_SEPARATOR)
795 pos.x -= sbt_field_size[i];
800 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
801 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
804 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
805 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
806 pos.x -= hud_fontsize.x;
811 pos.y += 1.25 * hud_fontsize.y;
815 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
817 TC(bool, is_self); TC(int, pl_number);
819 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
821 vector h_pos = item_pos;
822 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
823 // alternated rows highlighting
825 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
826 else if((sbt_highlight) && (!(pl_number % 2)))
827 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
829 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
831 vector pos = item_pos;
832 pos.x += hud_fontsize.x * 0.5;
833 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
834 vector tmp = '0 0 0';
836 PlayerScoreField field;
837 for(i = 0; i < sbt_num_fields; ++i)
839 field = sbt_field[i];
840 if(field == SP_SEPARATOR)
843 if(is_spec && field != SP_NAME && field != SP_PING) {
844 pos.x += sbt_field_size[i] + hud_fontsize.x;
847 str = Scoreboard_GetField(pl, field);
848 str = Scoreboard_FixColumnWidth(i, str);
850 pos.x += sbt_field_size[i] + hud_fontsize.x;
852 if(field == SP_NAME) {
853 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
854 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
856 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
857 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
860 tmp.x = sbt_field_size[i] + hud_fontsize.x;
861 if(sbt_field_icon0 != "")
862 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
863 if(sbt_field_icon1 != "")
864 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
865 if(sbt_field_icon2 != "")
866 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
869 if(sbt_field[i] == SP_SEPARATOR)
871 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
872 for(i = sbt_num_fields-1; i > 0; --i)
874 field = sbt_field[i];
875 if(field == SP_SEPARATOR)
878 if(is_spec && field != SP_NAME && field != SP_PING) {
879 pos.x -= sbt_field_size[i] + hud_fontsize.x;
883 str = Scoreboard_GetField(pl, field);
884 str = Scoreboard_FixColumnWidth(i, str);
886 if(field == SP_NAME) {
887 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
888 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
890 tmp.x = sbt_fixcolumnwidth_len;
891 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
894 tmp.x = sbt_field_size[i];
895 if(sbt_field_icon0 != "")
896 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
897 if(sbt_field_icon1 != "")
898 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
899 if(sbt_field_icon2 != "")
900 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
901 pos.x -= sbt_field_size[i] + hud_fontsize.x;
906 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
909 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
912 vector h_pos = item_pos;
913 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
915 bool complete = (this_team == NUM_SPECTATOR);
918 if((sbt_highlight) && (!(pl_number % 2)))
919 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
921 vector pos = item_pos;
922 pos.x += hud_fontsize.x * 0.5;
923 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
925 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
927 width_limit -= stringwidth("...", false, hud_fontsize);
928 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
929 static float max_name_width = 0;
932 float min_fieldsize = 0;
933 float fieldpadding = hud_fontsize.x * 0.25;
934 if(this_team == NUM_SPECTATOR)
936 if(autocvar_hud_panel_scoreboard_spectators_showping)
937 min_fieldsize = stringwidth("999", false, hud_fontsize);
939 else if(autocvar_hud_panel_scoreboard_others_showscore)
940 min_fieldsize = stringwidth("99", false, hud_fontsize);
941 for(i = 0; pl; pl = pl.sort_next)
943 if(pl.team != this_team)
949 if(this_team == NUM_SPECTATOR)
951 if(autocvar_hud_panel_scoreboard_spectators_showping)
952 field = Scoreboard_GetField(pl, SP_PING);
954 else if(autocvar_hud_panel_scoreboard_others_showscore)
955 field = Scoreboard_GetField(pl, SP_SCORE);
957 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
958 float column_width = stringwidth(str, true, hud_fontsize);
959 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
961 if(column_width > max_name_width)
962 max_name_width = column_width;
963 column_width = max_name_width;
967 fieldsize = stringwidth(field, false, hud_fontsize);
968 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
971 if(pos.x + column_width > width_limit)
976 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
981 pos.x = item_pos.x + hud_fontsize.x * 0.5;
982 pos.y += hud_fontsize.y * 1.25;
986 vector name_pos = pos;
987 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
988 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
989 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
992 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
993 h_size.y = hud_fontsize.y;
994 vector field_pos = pos;
995 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
996 field_pos.x += column_width - h_size.x;
998 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
999 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1000 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1002 pos.x += column_width;
1003 pos.x += hud_fontsize.x;
1005 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
1008 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1010 int max_players = 999;
1011 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1013 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1016 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1017 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1018 height /= team_count;
1021 height -= panel_bg_padding * 2; // - padding
1022 max_players = floor(height / (hud_fontsize.y * 1.25));
1023 if(max_players <= 1)
1025 if(max_players == tm.team_size)
1030 entity me = playerslots[current_player];
1032 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1033 panel_size.y += panel_bg_padding * 2;
1036 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1037 if(panel.current_panel_bg != "0")
1038 end_pos.y += panel_bg_border * 2;
1040 if(panel_bg_padding)
1042 panel_pos += '1 1 0' * panel_bg_padding;
1043 panel_size -= '2 2 0' * panel_bg_padding;
1047 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1051 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1053 pos.y += 1.25 * hud_fontsize.y;
1056 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1058 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1061 // print header row and highlight columns
1062 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1064 // fill the table and draw the rows
1065 bool is_self = false;
1066 bool self_shown = false;
1068 for(pl = players.sort_next; pl; pl = pl.sort_next)
1070 if(pl.team != tm.team)
1072 if(i == max_players - 2 && pl != me)
1074 if(!self_shown && me.team == tm.team)
1076 Scoreboard_DrawItem(pos, rgb, me, true, i);
1078 pos.y += 1.25 * hud_fontsize.y;
1082 if(i >= max_players - 1)
1084 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1087 is_self = (pl.sv_entnum == current_player);
1088 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1091 pos.y += 1.25 * hud_fontsize.y;
1095 panel_size.x += panel_bg_padding * 2; // restore initial width
1099 bool Scoreboard_WouldDraw()
1101 if (MUTATOR_CALLHOOK(DrawScoreboard))
1103 else if (QuickMenu_IsOpened())
1105 else if (HUD_Radar_Clickable())
1107 else if (scoreboard_showscores)
1109 else if (intermission == 1)
1111 else if (intermission == 2)
1113 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1115 else if (scoreboard_showscores_force)
1120 float average_accuracy;
1121 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1123 WepSet weapons_stat = WepSet_GetFromStat();
1124 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1125 int disownedcnt = 0;
1127 FOREACH(Weapons, it != WEP_Null, {
1128 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1130 WepSet set = it.m_wepset;
1131 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1133 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1140 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1141 if (weapon_cnt <= 0) return pos;
1144 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1146 int columnns = ceil(weapon_cnt / rows);
1148 float weapon_height = 29;
1149 float height = hud_fontsize.y + weapon_height;
1151 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1152 pos.y += 1.25 * hud_fontsize.y;
1153 if(panel.current_panel_bg != "0")
1154 pos.y += panel_bg_border;
1157 panel_size.y = height * rows;
1158 panel_size.y += panel_bg_padding * 2;
1161 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1162 if(panel.current_panel_bg != "0")
1163 end_pos.y += panel_bg_border * 2;
1165 if(panel_bg_padding)
1167 panel_pos += '1 1 0' * panel_bg_padding;
1168 panel_size -= '2 2 0' * panel_bg_padding;
1172 vector tmp = panel_size;
1174 float weapon_width = tmp.x / columnns / rows;
1177 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1181 // column highlighting
1182 for (int i = 0; i < columnns; ++i)
1184 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1187 for (int i = 0; i < rows; ++i)
1188 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1191 average_accuracy = 0;
1192 int weapons_with_stats = 0;
1194 pos.x += weapon_width / 2;
1196 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1199 Accuracy_LoadColors();
1201 float oldposx = pos.x;
1205 FOREACH(Weapons, it != WEP_Null, {
1206 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1208 WepSet set = it.m_wepset;
1209 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1213 if (weapon_stats >= 0)
1214 weapon_alpha = sbt_fg_alpha;
1216 weapon_alpha = 0.2 * sbt_fg_alpha;
1219 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1221 if (weapon_stats >= 0) {
1222 weapons_with_stats += 1;
1223 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1226 s = sprintf("%d%%", weapon_stats * 100);
1229 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1231 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1232 rgb = Accuracy_GetColor(weapon_stats);
1234 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1236 tmpos.x += weapon_width * rows;
1237 pos.x += weapon_width * rows;
1238 if (rows == 2 && column == columnns - 1) {
1246 if (weapons_with_stats)
1247 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1249 panel_size.x += panel_bg_padding * 2; // restore initial width
1253 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1255 pos.x += hud_fontsize.x * 0.25;
1256 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1257 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1258 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1260 pos.y += hud_fontsize.y;
1265 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1266 float stat_secrets_found, stat_secrets_total;
1267 float stat_monsters_killed, stat_monsters_total;
1271 // get monster stats
1272 stat_monsters_killed = STAT(MONSTERS_KILLED);
1273 stat_monsters_total = STAT(MONSTERS_TOTAL);
1275 // get secrets stats
1276 stat_secrets_found = STAT(SECRETS_FOUND);
1277 stat_secrets_total = STAT(SECRETS_TOTAL);
1279 // get number of rows
1280 if(stat_secrets_total)
1282 if(stat_monsters_total)
1285 // if no rows, return
1289 // draw table header
1290 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1291 pos.y += 1.25 * hud_fontsize.y;
1292 if(panel.current_panel_bg != "0")
1293 pos.y += panel_bg_border;
1296 panel_size.y = hud_fontsize.y * rows;
1297 panel_size.y += panel_bg_padding * 2;
1300 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1301 if(panel.current_panel_bg != "0")
1302 end_pos.y += panel_bg_border * 2;
1304 if(panel_bg_padding)
1306 panel_pos += '1 1 0' * panel_bg_padding;
1307 panel_size -= '2 2 0' * panel_bg_padding;
1311 vector tmp = panel_size;
1314 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1317 if(stat_monsters_total)
1319 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1320 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1324 if(stat_secrets_total)
1326 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1327 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1330 panel_size.x += panel_bg_padding * 2; // restore initial width
1335 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1338 RANKINGS_RECEIVED_CNT = 0;
1339 for (i=RANKINGS_CNT-1; i>=0; --i)
1341 ++RANKINGS_RECEIVED_CNT;
1343 if (RANKINGS_RECEIVED_CNT == 0)
1346 vector hl_rgb = rgb + '0.5 0.5 0.5';
1348 pos.y += hud_fontsize.y;
1349 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1350 pos.y += 1.25 * hud_fontsize.y;
1351 if(panel.current_panel_bg != "0")
1352 pos.y += panel_bg_border;
1357 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1359 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1364 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1366 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1370 float ranksize = 3 * hud_fontsize.x;
1371 float timesize = 5 * hud_fontsize.x;
1372 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1373 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1374 columns = min(columns, RANKINGS_RECEIVED_CNT);
1376 // expand name column to fill the entire row
1377 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1378 namesize += available_space;
1379 columnsize.x += available_space;
1381 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1382 panel_size.y += panel_bg_padding * 2;
1386 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1387 if(panel.current_panel_bg != "0")
1388 end_pos.y += panel_bg_border * 2;
1390 if(panel_bg_padding)
1392 panel_pos += '1 1 0' * panel_bg_padding;
1393 panel_size -= '2 2 0' * panel_bg_padding;
1399 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1401 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1403 int column = 0, j = 0;
1404 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1411 if(grecordholder[i] == entcs_GetName(player_localnum))
1412 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1413 else if(!((j + column) & 1) && sbt_highlight)
1414 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1416 str = count_ordinal(i+1);
1417 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1418 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1419 str = grecordholder[i];
1421 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1422 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1424 pos.y += 1.25 * hud_fontsize.y;
1426 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1430 pos.x += panel_size.x / columns;
1431 pos.y = panel_pos.y;
1435 panel_size.x += panel_bg_padding * 2; // restore initial width
1439 void Scoreboard_Draw()
1441 if(!autocvar__hud_configure)
1443 if(!hud_draw_maximized) return;
1445 // frametime checks allow to toggle the scoreboard even when the game is paused
1446 if(scoreboard_active) {
1447 if(hud_configure_menu_open == 1)
1448 scoreboard_fade_alpha = 1;
1449 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1450 if (scoreboard_fadeinspeed && frametime)
1451 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1453 scoreboard_fade_alpha = 1;
1454 if(hud_fontsize_str != autocvar_hud_fontsize)
1456 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1457 Scoreboard_initFieldSizes();
1458 if(hud_fontsize_str)
1459 strunzone(hud_fontsize_str);
1460 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1464 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1465 if (scoreboard_fadeoutspeed && frametime)
1466 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1468 scoreboard_fade_alpha = 0;
1471 if (!scoreboard_fade_alpha)
1475 scoreboard_fade_alpha = 0;
1477 if (autocvar_hud_panel_scoreboard_dynamichud)
1480 HUD_Scale_Disable();
1482 if(scoreboard_fade_alpha <= 0)
1484 panel_fade_alpha *= scoreboard_fade_alpha;
1485 HUD_Panel_LoadCvars();
1487 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1488 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1489 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1490 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1491 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1492 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1494 // don't overlap with con_notify
1495 if(!autocvar__hud_configure)
1496 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1498 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1499 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1500 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1501 panel_size.x = fixed_scoreboard_width;
1503 Scoreboard_UpdatePlayerTeams();
1505 vector pos = panel_pos;
1510 vector sb_heading_fontsize;
1511 sb_heading_fontsize = hud_fontsize * 2;
1512 draw_beginBoldFont();
1513 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1516 pos.y += sb_heading_fontsize.y;
1517 if(panel.current_panel_bg != "0")
1518 pos.y += panel_bg_border;
1520 // Draw the scoreboard
1521 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1524 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1528 vector panel_bg_color_save = panel_bg_color;
1529 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1530 if(panel.current_panel_bg != "0")
1531 team_score_baseoffset.x -= panel_bg_border;
1532 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1534 if(tm.team == NUM_SPECTATOR)
1539 draw_beginBoldFont();
1540 vector rgb = Team_ColorRGB(tm.team);
1541 str = ftos(tm.(teamscores(ts_primary)));
1542 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1544 if(ts_primary != ts_secondary)
1546 str = ftos(tm.(teamscores(ts_secondary)));
1547 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1550 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1551 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1552 else if(panel_bg_color_team > 0)
1553 panel_bg_color = rgb * panel_bg_color_team;
1555 panel_bg_color = rgb;
1556 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1558 panel_bg_color = panel_bg_color_save;
1562 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1563 if(tm.team != NUM_SPECTATOR)
1565 // display it anyway
1566 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1569 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1571 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1572 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1574 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1575 if(race_speedaward) {
1576 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, race_speedaward_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1577 pos.y += 1.25 * hud_fontsize.y;
1579 if(race_speedaward_alltimebest) {
1580 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, race_speedaward_alltimebest_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1581 pos.y += 1.25 * hud_fontsize.y;
1583 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1586 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1589 for(pl = players.sort_next; pl; pl = pl.sort_next)
1591 if(pl.team == NUM_SPECTATOR)
1593 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1594 if(tm.team == NUM_SPECTATOR)
1596 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1597 draw_beginBoldFont();
1598 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1600 pos.y += 1.25 * hud_fontsize.y;
1602 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1603 pos.y += 1.25 * hud_fontsize.y;
1609 // Print info string
1611 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1612 tl = STAT(TIMELIMIT);
1613 fl = STAT(FRAGLIMIT);
1614 ll = STAT(LEADLIMIT);
1615 if(gametype == MAPINFO_TYPE_LMS)
1618 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1623 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1627 str = strcat(str, _(" or"));
1630 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1631 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1632 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1633 TranslateScoresLabel(teamscores_label(ts_primary))));
1637 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1638 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1639 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1640 TranslateScoresLabel(scores_label(ps_primary))));
1645 if(tl > 0 || fl > 0)
1646 str = strcat(str, _(" or"));
1649 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1650 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1651 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1652 TranslateScoresLabel(teamscores_label(ts_primary))));
1656 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1657 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1658 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1659 TranslateScoresLabel(scores_label(ps_primary))));
1664 pos.y += 1.2 * hud_fontsize.y;
1665 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1667 // print information about respawn status
1668 float respawn_time = STAT(RESPAWN_TIME);
1672 if(respawn_time < 0)
1674 // a negative number means we are awaiting respawn, time value is still the same
1675 respawn_time *= -1; // remove mark now that we checked it
1677 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1678 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1680 str = sprintf(_("^1Respawning in ^3%s^1..."),
1681 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1682 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1684 count_seconds(ceil(respawn_time - time))
1688 else if(time < respawn_time)
1690 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
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)
1699 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1701 pos.y += 1.2 * hud_fontsize.y;
1702 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1705 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;