1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
13 float sbt_fg_alpha_self;
15 float sbt_highlight_alpha;
16 float sbt_highlight_alpha_self;
18 // provide basic panel cvars to old clients
19 // TODO remove them after a future release (0.8.2+)
20 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
21 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
22 string autocvar_hud_panel_scoreboard_bg = "border_default";
23 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
24 string autocvar_hud_panel_scoreboard_bg_color_team = "";
25 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
26 string autocvar_hud_panel_scoreboard_bg_border = "";
27 string autocvar_hud_panel_scoreboard_bg_padding = "";
29 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
30 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
31 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
32 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0.7;
33 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
34 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
35 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
36 bool autocvar_hud_panel_scoreboard_table_highlight = true;
37 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
38 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
39 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
41 bool autocvar_hud_panel_scoreboard_accuracy = true;
42 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
43 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
45 bool autocvar_hud_panel_scoreboard_dynamichud = false;
48 void drawstringright(vector, string, vector, vector, float, float);
49 void drawstringcenter(vector, string, vector, vector, float, float);
51 // wrapper to put all possible scores titles through gettext
52 string TranslateScoresLabel(string l)
56 case "bckills": return CTX(_("SCO^bckills"));
57 case "bctime": return CTX(_("SCO^bctime"));
58 case "caps": return CTX(_("SCO^caps"));
59 case "captime": return CTX(_("SCO^captime"));
60 case "deaths": return CTX(_("SCO^deaths"));
61 case "destroyed": return CTX(_("SCO^destroyed"));
62 case "dmg": return CTX(_("SCO^dmg"));
63 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
64 case "drops": return CTX(_("SCO^drops"));
65 case "faults": return CTX(_("SCO^faults"));
66 case "fckills": return CTX(_("SCO^fckills"));
67 case "goals": return CTX(_("SCO^goals"));
68 case "kckills": return CTX(_("SCO^kckills"));
69 case "kdratio": return CTX(_("SCO^kdratio"));
70 case "k/d": return CTX(_("SCO^k/d"));
71 case "kd": return CTX(_("SCO^kd"));
72 case "kdr": return CTX(_("SCO^kdr"));
73 case "kills": return CTX(_("SCO^kills"));
74 case "laps": return CTX(_("SCO^laps"));
75 case "lives": return CTX(_("SCO^lives"));
76 case "losses": return CTX(_("SCO^losses"));
77 case "name": return CTX(_("SCO^name"));
78 case "sum": return CTX(_("SCO^sum"));
79 case "nick": return CTX(_("SCO^nick"));
80 case "objectives": return CTX(_("SCO^objectives"));
81 case "pickups": return CTX(_("SCO^pickups"));
82 case "ping": return CTX(_("SCO^ping"));
83 case "pl": return CTX(_("SCO^pl"));
84 case "pushes": return CTX(_("SCO^pushes"));
85 case "rank": return CTX(_("SCO^rank"));
86 case "returns": return CTX(_("SCO^returns"));
87 case "revivals": return CTX(_("SCO^revivals"));
88 case "score": return CTX(_("SCO^score"));
89 case "suicides": return CTX(_("SCO^suicides"));
90 case "takes": return CTX(_("SCO^takes"));
91 case "ticks": return CTX(_("SCO^ticks"));
96 void Scoreboard_InitScores()
100 ps_primary = ps_secondary = NULL;
101 ts_primary = ts_secondary = -1;
102 FOREACH(Scores, true, {
103 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
104 if(f == SFL_SORT_PRIO_PRIMARY)
106 if(f == SFL_SORT_PRIO_SECONDARY)
109 if(ps_secondary == NULL)
110 ps_secondary = ps_primary;
112 for(i = 0; i < MAX_TEAMSCORE; ++i)
114 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
115 if(f == SFL_SORT_PRIO_PRIMARY)
117 if(f == SFL_SORT_PRIO_SECONDARY)
120 if(ts_secondary == -1)
121 ts_secondary = ts_primary;
123 Cmd_Scoreboard_SetFields(0);
126 float SetTeam(entity pl, float Team);
128 void Scoreboard_UpdatePlayerTeams()
135 for(pl = players.sort_next; pl; pl = pl.sort_next)
138 Team = entcs_GetScoreTeam(pl.sv_entnum);
139 if(SetTeam(pl, Team))
142 Scoreboard_UpdatePlayerPos(pl);
146 pl = players.sort_next;
151 print(strcat("PNUM: ", ftos(num), "\n"));
156 int Scoreboard_CompareScore(int vl, int vr, int f)
158 TC(int, vl); TC(int, vr); TC(int, f);
159 if(f & SFL_ZERO_IS_WORST)
161 if(vl == 0 && vr != 0)
163 if(vl != 0 && vr == 0)
167 return IS_INCREASING(f);
169 return IS_DECREASING(f);
173 float Scoreboard_ComparePlayerScores(entity left, entity right)
176 vl = entcs_GetTeam(left.sv_entnum);
177 vr = entcs_GetTeam(right.sv_entnum);
189 if(vl == NUM_SPECTATOR)
191 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
193 if(!left.gotscores && right.gotscores)
198 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
202 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
206 FOREACH(Scores, true, {
207 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
208 if (r >= 0) return r;
211 if (left.sv_entnum < right.sv_entnum)
217 void Scoreboard_UpdatePlayerPos(entity player)
220 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
222 SORT_SWAP(player, ent);
224 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
226 SORT_SWAP(ent, player);
230 float Scoreboard_CompareTeamScores(entity left, entity right)
234 if(left.team == NUM_SPECTATOR)
236 if(right.team == NUM_SPECTATOR)
239 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
243 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
247 for(i = 0; i < MAX_TEAMSCORE; ++i)
249 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
254 if (left.team < right.team)
260 void Scoreboard_UpdateTeamPos(entity Team)
263 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
265 SORT_SWAP(Team, ent);
267 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
269 SORT_SWAP(ent, Team);
273 void Cmd_Scoreboard_Help()
275 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
276 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
277 LOG_INFO(_("Usage:\n"));
278 LOG_INFO(_("^2scoreboard_columns_set default\n"));
279 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
280 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
281 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
284 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
285 LOG_INFO(_("^3ping^7 Ping time\n"));
286 LOG_INFO(_("^3pl^7 Packet loss\n"));
287 LOG_INFO(_("^3elo^7 Player ELO\n"));
288 LOG_INFO(_("^3kills^7 Number of kills\n"));
289 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
290 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
291 LOG_INFO(_("^3frags^7 kills - suicides\n"));
292 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
293 LOG_INFO(_("^3dmg^7 The total damage done\n"));
294 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
295 LOG_INFO(_("^3sum^7 frags - deaths\n"));
296 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
297 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
298 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
299 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
300 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
301 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
302 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
303 LOG_INFO(_("^3rank^7 Player rank\n"));
304 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
305 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
306 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
307 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
308 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
309 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
310 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
311 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
312 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
313 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
314 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
315 LOG_INFO(_("^3score^7 Total score\n"));
318 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
319 "of game types, then a slash, to make the field show up only in these\n"
320 "or in all but these game types. You can also specify 'all' as a\n"
321 "field to show all fields available for the current game mode.\n\n"));
323 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
324 "include/exclude ALL teams/noteams game modes.\n\n"));
326 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
327 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
328 "right of the vertical bar aligned to the right.\n"));
329 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
330 "other gamemodes except DM.\n"));
333 // NOTE: adding a gametype with ? to not warn for an optional field
334 // make sure it's excluded in a previous exclusive rule, if any
335 // otherwise the previous exclusive rule warns anyway
336 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
337 #define SCOREBOARD_DEFAULT_COLUMNS \
339 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
340 " -teams,lms/deaths +ft,tdm/deaths" \
341 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
342 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
343 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
344 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
345 " +lms/lives +lms/rank" \
346 " +kh/caps +kh/pushes +kh/destroyed" \
347 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
348 " +as/objectives +nb/faults +nb/goals" \
349 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
350 " -lms,rc,cts,inv,nb/score"
352 void Cmd_Scoreboard_SetFields(int argc)
357 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
362 // set up a temporary scoreboard layout
363 // no layout can be properly set up until score_info data haven't been received
364 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
365 ps_primary = SP_SCORE;
366 scores_label(ps_primary) = strzone("score");
367 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
370 // TODO: re enable with gametype dependant cvars?
371 if(argc < 3) // no arguments provided
372 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
375 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
379 if(argv(2) == "default")
380 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
381 else if(argv(2) == "all")
384 s = "ping pl name |";
385 FOREACH(Scores, true, {
387 if(it != ps_secondary)
388 if(scores_label(it) != "")
389 s = strcat(s, " ", scores_label(it));
391 if(ps_secondary != ps_primary)
392 s = strcat(s, " ", scores_label(ps_secondary));
393 s = strcat(s, " ", scores_label(ps_primary));
394 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
401 hud_fontsize = HUD_GetFontsize("hud_fontsize");
403 for(i = 1; i < argc - 1; ++i)
409 if(substring(str, 0, 1) == "?")
412 str = substring(str, 1, strlen(str) - 1);
415 slash = strstrofs(str, "/", 0);
418 pattern = substring(str, 0, slash);
419 str = substring(str, slash + 1, strlen(str) - (slash + 1));
421 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
425 strunzone(sbt_field_title[sbt_num_fields]);
426 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
427 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
428 str = strtolower(str);
433 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
434 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
435 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
436 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
437 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
438 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
439 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
440 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
441 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
444 FOREACH(Scores, true, {
445 if (str == strtolower(scores_label(it))) {
447 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
457 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
461 sbt_field[sbt_num_fields] = j;
464 if(j == ps_secondary)
470 if(sbt_num_fields >= MAX_SBT_FIELDS)
474 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
476 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
478 if(ps_primary == ps_secondary)
480 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
482 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
486 strunzone(sbt_field_title[sbt_num_fields]);
487 for(i = sbt_num_fields; i > 0; --i)
489 sbt_field_title[i] = sbt_field_title[i-1];
490 sbt_field_size[i] = sbt_field_size[i-1];
491 sbt_field[i] = sbt_field[i-1];
493 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
494 sbt_field[0] = SP_NAME;
496 LOG_INFO("fixed missing field 'name'\n");
500 strunzone(sbt_field_title[sbt_num_fields]);
501 for(i = sbt_num_fields; i > 1; --i)
503 sbt_field_title[i] = sbt_field_title[i-1];
504 sbt_field_size[i] = sbt_field_size[i-1];
505 sbt_field[i] = sbt_field[i-1];
507 sbt_field_title[1] = strzone("|");
508 sbt_field[1] = SP_SEPARATOR;
509 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
511 LOG_INFO("fixed missing field '|'\n");
514 else if(!have_separator)
516 strunzone(sbt_field_title[sbt_num_fields]);
517 sbt_field_title[sbt_num_fields] = strzone("|");
518 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
519 sbt_field[sbt_num_fields] = SP_SEPARATOR;
521 LOG_INFO("fixed missing field '|'\n");
525 strunzone(sbt_field_title[sbt_num_fields]);
526 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
527 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
528 sbt_field[sbt_num_fields] = ps_secondary;
530 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
536 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
537 sbt_field[sbt_num_fields] = ps_primary;
539 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
543 sbt_field[sbt_num_fields] = SP_END;
547 vector sbt_field_rgb;
548 string sbt_field_icon0;
549 string sbt_field_icon1;
550 string sbt_field_icon2;
551 vector sbt_field_icon0_rgb;
552 vector sbt_field_icon1_rgb;
553 vector sbt_field_icon2_rgb;
554 float sbt_field_icon0_alpha;
555 float sbt_field_icon1_alpha;
556 float sbt_field_icon2_alpha;
557 string Scoreboard_GetField(entity pl, PlayerScoreField field)
559 float tmp, num, denom;
562 sbt_field_rgb = '1 1 1';
563 sbt_field_icon0 = "";
564 sbt_field_icon1 = "";
565 sbt_field_icon2 = "";
566 sbt_field_icon0_rgb = '1 1 1';
567 sbt_field_icon1_rgb = '1 1 1';
568 sbt_field_icon2_rgb = '1 1 1';
569 sbt_field_icon0_alpha = 1;
570 sbt_field_icon1_alpha = 1;
571 sbt_field_icon2_alpha = 1;
576 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
577 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
581 tmp = max(0, min(220, f-80)) / 220;
582 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
588 f = pl.ping_packetloss;
589 tmp = pl.ping_movementloss;
590 if(f == 0 && tmp == 0)
592 str = ftos(ceil(f * 100));
594 str = strcat(str, "~", ftos(ceil(tmp * 100)));
595 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
596 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
600 if(ready_waiting && pl.ready)
602 sbt_field_icon0 = "gfx/scoreboard/player_ready";
606 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
608 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
609 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
610 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
611 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
612 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
615 return entcs_GetName(pl.sv_entnum);
618 f = pl.(scores(SP_KILLS));
619 f -= pl.(scores(SP_SUICIDES));
623 num = pl.(scores(SP_KILLS));
624 denom = pl.(scores(SP_DEATHS));
627 sbt_field_rgb = '0 1 0';
628 str = sprintf("%d", num);
629 } else if(num <= 0) {
630 sbt_field_rgb = '1 0 0';
631 str = sprintf("%.1f", num/denom);
633 str = sprintf("%.1f", num/denom);
637 f = pl.(scores(SP_KILLS));
638 f -= pl.(scores(SP_DEATHS));
641 sbt_field_rgb = '0 1 0';
643 sbt_field_rgb = '1 1 1';
645 sbt_field_rgb = '1 0 0';
651 float elo = pl.(scores(SP_ELO));
653 case -1: return "...";
654 case -2: return _("N/A");
655 default: return ftos(elo);
660 num = pl.(scores(SP_DMG));
663 str = sprintf("%.1f k", num/denom);
667 num = pl.(scores(SP_DMGTAKEN));
670 str = sprintf("%.1f k", num/denom);
674 tmp = pl.(scores(field));
675 f = scores_flags(field);
676 if(field == ps_primary)
677 sbt_field_rgb = '1 1 0';
678 else if(field == ps_secondary)
679 sbt_field_rgb = '0 1 1';
681 sbt_field_rgb = '1 1 1';
682 return ScoreString(f, tmp);
687 float sbt_fixcolumnwidth_len;
688 float sbt_fixcolumnwidth_iconlen;
689 float sbt_fixcolumnwidth_marginlen;
691 string Scoreboard_FixColumnWidth(int i, string str)
696 PlayerScoreField field = sbt_field[i];
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(field == SP_NAME) // name gets all remaining space
735 namesize = panel_size.x;
736 for(j = 0; j < sbt_num_fields; ++j)
738 if (sbt_field[i] != SP_SEPARATOR)
739 namesize -= sbt_field_size[j] + hud_fontsize.x;
740 sbt_field_size[i] = namesize;
742 if (sbt_fixcolumnwidth_iconlen != 0)
743 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
744 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
745 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
748 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
750 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 if(sbt_field_size[i] < f)
752 sbt_field_size[i] = f;
757 vector Scoreboard_DrawHeader(vector pos, vector rgb)
760 vector column_dim = eY * panel_size.y;
761 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
762 pos.x += hud_fontsize.x * 0.5;
763 for(i = 0; i < sbt_num_fields; ++i)
765 if(sbt_field[i] == SP_SEPARATOR)
767 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
770 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
771 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
772 pos.x += column_dim.x;
774 if(sbt_field[i] == SP_SEPARATOR)
776 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
777 for(i = sbt_num_fields - 1; i > 0; --i)
779 if(sbt_field[i] == SP_SEPARATOR)
782 pos.x -= sbt_field_size[i];
787 if (i == sbt_num_fields-1)
788 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
790 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);
794 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
795 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
796 pos.x -= hud_fontsize.x;
801 pos.y += 1.25 * hud_fontsize.y;
805 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
807 TC(bool, is_self); TC(int, pl_number);
809 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
810 if(is_spec && !is_self)
813 vector h_pos = item_pos;
814 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
815 // alternated rows highlighting
817 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
818 else if((sbt_highlight) && (!(pl_number % 2)))
819 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
821 vector pos = item_pos;
822 pos.x += hud_fontsize.x * 0.5;
823 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
824 vector tmp = '0 0 0';
826 PlayerScoreField field;
827 for(i = 0; i < sbt_num_fields; ++i)
829 field = sbt_field[i];
830 if(field == SP_SEPARATOR)
833 if(is_spec && field != SP_NAME && field != SP_PING) {
834 pos.x += sbt_field_size[i] + hud_fontsize.x;
837 str = Scoreboard_GetField(pl, field);
838 str = Scoreboard_FixColumnWidth(i, str);
840 pos.x += sbt_field_size[i] + hud_fontsize.x;
842 if(field == SP_NAME) {
843 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
845 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
847 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
849 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
851 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
853 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
856 tmp.x = sbt_field_size[i] + hud_fontsize.x;
857 if(sbt_field_icon0 != "")
859 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
861 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
862 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, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
866 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
867 if(sbt_field_icon2 != "")
869 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
871 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
874 if(sbt_field[i] == SP_SEPARATOR)
876 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
877 for(i = sbt_num_fields-1; i > 0; --i)
879 field = sbt_field[i];
880 if(field == SP_SEPARATOR)
883 if(is_spec && field != SP_NAME && field != SP_PING) {
884 pos.x -= sbt_field_size[i] + hud_fontsize.x;
888 str = Scoreboard_GetField(pl, field);
889 str = Scoreboard_FixColumnWidth(i, str);
891 if(field == SP_NAME) {
892 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
894 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
896 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_fixcolumnwidth_len;
900 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
902 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
905 tmp.x = sbt_field_size[i];
906 if(sbt_field_icon0 != "")
908 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
910 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
911 if(sbt_field_icon1 != "")
913 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
915 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
916 if(sbt_field_icon2 != "")
918 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
920 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
921 pos.x -= sbt_field_size[i] + hud_fontsize.x;
926 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
929 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
934 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
935 panel_size.y += panel_bg_padding * 2;
936 HUD_Panel_DrawBg(scoreboard_fade_alpha);
938 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
939 if(panel.current_panel_bg != "0")
940 end_pos.y += panel_bg_border * 2;
944 panel_pos += '1 1 0' * panel_bg_padding;
945 panel_size -= '2 2 0' * panel_bg_padding;
949 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
953 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
955 pos.y += 1.25 * hud_fontsize.y;
958 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
960 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
963 // print header row and highlight columns
964 pos = Scoreboard_DrawHeader(panel_pos, rgb);
966 // fill the table and draw the rows
969 for(pl = players.sort_next; pl; pl = pl.sort_next)
971 if(pl.team != tm.team)
973 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
974 pos.y += 1.25 * hud_fontsize.y;
978 for(pl = players.sort_next; pl; pl = pl.sort_next)
980 if(pl.team == NUM_SPECTATOR)
982 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
983 pos.y += 1.25 * hud_fontsize.y;
987 panel_size.x += panel_bg_padding * 2; // restore initial width
991 float Scoreboard_WouldDraw() {
992 if (QuickMenu_IsOpened())
994 else if (HUD_Radar_Clickable())
996 else if (scoreboard_showscores)
998 else if (intermission == 1)
1000 else if (intermission == 2)
1002 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1004 else if (scoreboard_showscores_force)
1009 float average_accuracy;
1010 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1012 WepSet weapons_stat = WepSet_GetFromStat();
1013 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1014 int disownedcnt = 0;
1015 FOREACH(Weapons, it != WEP_Null, {
1016 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1018 WepSet set = it.m_wepset;
1019 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1023 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1024 if (weapon_cnt <= 0) return pos;
1027 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1029 int columnns = ceil(weapon_cnt / rows);
1033 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1034 pos.y += 1.25 * hud_fontsize.y;
1035 if(panel.current_panel_bg != "0")
1036 pos.y += panel_bg_border;
1039 panel_size.y = height * rows;
1040 panel_size.y += panel_bg_padding * 2;
1041 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1043 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1044 if(panel.current_panel_bg != "0")
1045 end_pos.y += panel_bg_border * 2;
1047 if(panel_bg_padding)
1049 panel_pos += '1 1 0' * panel_bg_padding;
1050 panel_size -= '2 2 0' * panel_bg_padding;
1054 vector tmp = panel_size;
1056 float fontsize = height * 1/3;
1057 float weapon_height = height * 2/3;
1058 float weapon_width = tmp.x / columnns / rows;
1061 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1065 // column highlighting
1066 for (int i = 0; i < columnns; ++i)
1068 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1071 for (int i = 0; i < rows; ++i)
1072 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1075 average_accuracy = 0;
1076 int weapons_with_stats = 0;
1078 pos.x += weapon_width / 2;
1080 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1083 Accuracy_LoadColors();
1085 float oldposx = pos.x;
1089 FOREACH(Weapons, it != WEP_Null, {
1090 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1092 WepSet set = it.m_wepset;
1093 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1097 if (weapon_stats >= 0)
1098 weapon_alpha = sbt_fg_alpha;
1100 weapon_alpha = 0.2 * sbt_fg_alpha;
1103 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1105 if (weapon_stats >= 0) {
1106 weapons_with_stats += 1;
1107 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1110 s = sprintf("%d%%", weapon_stats * 100);
1113 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1115 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1116 rgb = Accuracy_GetColor(weapon_stats);
1118 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1120 tmpos.x += weapon_width * rows;
1121 pos.x += weapon_width * rows;
1122 if (rows == 2 && column == columnns - 1) {
1130 if (weapons_with_stats)
1131 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1133 panel_size.x += panel_bg_padding * 2; // restore initial width
1137 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1139 pos.x += hud_fontsize.x * 0.25;
1140 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1141 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1142 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1144 pos.y += hud_fontsize.y;
1149 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1150 float stat_secrets_found, stat_secrets_total;
1151 float stat_monsters_killed, stat_monsters_total;
1155 // get monster stats
1156 stat_monsters_killed = STAT(MONSTERS_KILLED);
1157 stat_monsters_total = STAT(MONSTERS_TOTAL);
1159 // get secrets stats
1160 stat_secrets_found = STAT(SECRETS_FOUND);
1161 stat_secrets_total = STAT(SECRETS_TOTAL);
1163 // get number of rows
1164 if(stat_secrets_total)
1166 if(stat_monsters_total)
1169 // if no rows, return
1173 // draw table header
1174 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1175 pos.y += 1.25 * hud_fontsize.y;
1176 if(panel.current_panel_bg != "0")
1177 pos.y += panel_bg_border;
1180 panel_size.y = hud_fontsize.y * rows;
1181 panel_size.y += panel_bg_padding * 2;
1182 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1184 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1185 if(panel.current_panel_bg != "0")
1186 end_pos.y += panel_bg_border * 2;
1188 if(panel_bg_padding)
1190 panel_pos += '1 1 0' * panel_bg_padding;
1191 panel_size -= '2 2 0' * panel_bg_padding;
1195 vector tmp = panel_size;
1198 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1201 if(stat_monsters_total)
1203 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1204 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1208 if(stat_secrets_total)
1210 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1211 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1214 panel_size.x += panel_bg_padding * 2; // restore initial width
1219 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1222 RANKINGS_RECEIVED_CNT = 0;
1223 for (i=RANKINGS_CNT-1; i>=0; --i)
1225 ++RANKINGS_RECEIVED_CNT;
1227 if (RANKINGS_RECEIVED_CNT == 0)
1230 vector hl_rgb = rgb + '0.5 0.5 0.5';
1232 pos.y += hud_fontsize.y;
1233 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1234 pos.y += 1.25 * hud_fontsize.y;
1235 if(panel.current_panel_bg != "0")
1236 pos.y += panel_bg_border;
1239 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1240 panel_size.y += panel_bg_padding * 2;
1241 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1243 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1244 if(panel.current_panel_bg != "0")
1245 end_pos.y += panel_bg_border * 2;
1247 if(panel_bg_padding)
1249 panel_pos += '1 1 0' * panel_bg_padding;
1250 panel_size -= '2 2 0' * panel_bg_padding;
1254 vector tmp = panel_size;
1257 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1260 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1267 n = grecordholder[i];
1268 p = count_ordinal(i+1);
1269 if(grecordholder[i] == entcs_GetName(player_localnum))
1270 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1271 else if(!(i % 2) && sbt_highlight)
1272 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1273 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1274 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1275 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1276 pos.y += 1.25 * hud_fontsize.y;
1279 panel_size.x += panel_bg_padding * 2; // restore initial width
1283 void Scoreboard_Draw()
1285 if(!autocvar__hud_configure)
1287 // frametime checks allow to toggle the scoreboard even when the game is paused
1288 if(scoreboard_active) {
1289 if(menu_enabled == 1)
1290 scoreboard_fade_alpha = 1;
1291 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1292 if (scoreboard_fadeinspeed && frametime)
1293 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1295 scoreboard_fade_alpha = 1;
1298 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1299 if (scoreboard_fadeoutspeed && frametime)
1300 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1302 scoreboard_fade_alpha = 0;
1305 if (!scoreboard_fade_alpha)
1309 scoreboard_fade_alpha = 0;
1311 if (autocvar_hud_panel_scoreboard_dynamichud)
1314 HUD_Scale_Disable();
1316 float hud_fade_alpha_save = hud_fade_alpha;
1317 if(menu_enabled == 1)
1320 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1321 HUD_Panel_UpdateCvars();
1323 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1324 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1325 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1326 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1327 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1328 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1330 hud_fade_alpha = hud_fade_alpha_save;
1332 // don't overlap with con_notify
1333 if(!autocvar__hud_configure)
1334 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1336 Scoreboard_UpdatePlayerTeams();
1342 // Initializes position
1346 vector sb_heading_fontsize;
1347 sb_heading_fontsize = hud_fontsize * 2;
1348 draw_beginBoldFont();
1349 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1352 pos.y += sb_heading_fontsize.y;
1353 if(panel.current_panel_bg != "0")
1354 pos.y += panel_bg_border;
1356 // Draw the scoreboard
1357 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1360 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1364 vector panel_bg_color_save = panel_bg_color;
1365 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1366 if(panel.current_panel_bg != "0")
1367 team_score_baseoffset.x -= panel_bg_border;
1368 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1370 if(tm.team == NUM_SPECTATOR)
1372 if(!tm.team && teamplay)
1375 draw_beginBoldFont();
1376 vector rgb = Team_ColorRGB(tm.team);
1377 str = ftos(tm.(teamscores(ts_primary)));
1378 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1380 if(ts_primary != ts_secondary)
1382 str = ftos(tm.(teamscores(ts_secondary)));
1383 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);
1386 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1387 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1388 else if(panel_bg_color_team > 0)
1389 panel_bg_color = rgb * panel_bg_color_team;
1391 panel_bg_color = rgb;
1392 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1394 panel_bg_color = panel_bg_color_save;
1398 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1400 if(tm.team == NUM_SPECTATOR)
1402 if(!tm.team && teamplay)
1405 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1409 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1410 if(race_speedaward) {
1411 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);
1412 pos.y += 1.25 * hud_fontsize.y;
1414 if(race_speedaward_alltimebest) {
1415 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);
1416 pos.y += 1.25 * hud_fontsize.y;
1418 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1420 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1421 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1423 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1428 for(pl = players.sort_next; pl; pl = pl.sort_next)
1430 if(pl.team != NUM_SPECTATOR)
1432 pos.y += 1.25 * hud_fontsize.y;
1433 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1439 draw_beginBoldFont();
1440 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1442 pos.y += 1.25 * hud_fontsize.y;
1445 // Print info string
1447 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1448 tl = STAT(TIMELIMIT);
1449 fl = STAT(FRAGLIMIT);
1450 ll = STAT(LEADLIMIT);
1451 if(gametype == MAPINFO_TYPE_LMS)
1454 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1459 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1463 str = strcat(str, _(" or"));
1466 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1467 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1468 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1469 TranslateScoresLabel(teamscores_label(ts_primary))));
1473 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1474 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1475 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1476 TranslateScoresLabel(scores_label(ps_primary))));
1481 if(tl > 0 || fl > 0)
1482 str = strcat(str, _(" or"));
1485 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1486 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1487 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1488 TranslateScoresLabel(teamscores_label(ts_primary))));
1492 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1493 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1494 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1495 TranslateScoresLabel(scores_label(ps_primary))));
1500 pos.y += 1.2 * hud_fontsize.y;
1501 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1503 // print information about respawn status
1504 float respawn_time = STAT(RESPAWN_TIME);
1508 if(respawn_time < 0)
1510 // a negative number means we are awaiting respawn, time value is still the same
1511 respawn_time *= -1; // remove mark now that we checked it
1512 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1514 str = sprintf(_("^1Respawning in ^3%s^1..."),
1515 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1516 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1518 count_seconds(respawn_time - time)
1522 else if(time < respawn_time)
1524 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1525 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1526 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1528 count_seconds(respawn_time - time)
1532 else if(time >= respawn_time)
1533 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1535 pos.y += 1.2 * hud_fontsize.y;
1536 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1539 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;