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;
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^damage"));
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 "kd": return CTX(_("SCO^k/d"));
71 case "kdr": return CTX(_("SCO^kdr"));
72 case "kills": return CTX(_("SCO^kills"));
73 case "laps": return CTX(_("SCO^laps"));
74 case "lives": return CTX(_("SCO^lives"));
75 case "losses": return CTX(_("SCO^losses"));
76 case "name": return CTX(_("SCO^name"));
77 case "sum": return CTX(_("SCO^sum"));
78 case "nick": return CTX(_("SCO^nick"));
79 case "objectives": return CTX(_("SCO^objectives"));
80 case "pickups": return CTX(_("SCO^pickups"));
81 case "ping": return CTX(_("SCO^ping"));
82 case "pl": return CTX(_("SCO^pl"));
83 case "pushes": return CTX(_("SCO^pushes"));
84 case "rank": return CTX(_("SCO^rank"));
85 case "returns": return CTX(_("SCO^returns"));
86 case "revivals": return CTX(_("SCO^revivals"));
87 case "rounds": return CTX(_("SCO^rounds won"));
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;
361 return; // do nothing, we don't know gametype and scores yet
363 // sbt_fields uses strunzone on the titles!
364 if(!sbt_field_title[0])
365 for(i = 0; i < MAX_SBT_FIELDS; ++i)
366 sbt_field_title[i] = strzone("(null)");
368 // TODO: re enable with gametype dependant cvars?
369 if(argc < 3) // no arguments provided
370 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
373 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
377 if(argv(2) == "default")
378 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
379 else if(argv(2) == "all")
382 s = "ping pl name |";
383 FOREACH(Scores, true, {
385 if(it != ps_secondary)
386 if(scores_label(it) != "")
387 s = strcat(s, " ", scores_label(it));
389 if(ps_secondary != ps_primary)
390 s = strcat(s, " ", scores_label(ps_secondary));
391 s = strcat(s, " ", scores_label(ps_primary));
392 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
399 hud_fontsize = HUD_GetFontsize("hud_fontsize");
401 for(i = 1; i < argc - 1; ++i)
407 if(substring(str, 0, 1) == "?")
410 str = substring(str, 1, strlen(str) - 1);
413 slash = strstrofs(str, "/", 0);
416 pattern = substring(str, 0, slash);
417 str = substring(str, slash + 1, strlen(str) - (slash + 1));
419 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
423 strunzone(sbt_field_title[sbt_num_fields]);
424 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
425 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
426 str = strtolower(str);
431 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
432 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
433 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
434 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
435 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
436 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
437 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
438 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
439 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
442 FOREACH(Scores, true, {
443 if (str == strtolower(scores_label(it))) {
445 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
455 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
459 sbt_field[sbt_num_fields] = j;
462 if(j == ps_secondary)
468 if(sbt_num_fields >= MAX_SBT_FIELDS)
472 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
474 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
476 if(ps_primary == ps_secondary)
478 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
480 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
484 strunzone(sbt_field_title[sbt_num_fields]);
485 for(i = sbt_num_fields; i > 0; --i)
487 sbt_field_title[i] = sbt_field_title[i-1];
488 sbt_field_size[i] = sbt_field_size[i-1];
489 sbt_field[i] = sbt_field[i-1];
491 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
492 sbt_field[0] = SP_NAME;
494 LOG_INFO("fixed missing field 'name'\n");
498 strunzone(sbt_field_title[sbt_num_fields]);
499 for(i = sbt_num_fields; i > 1; --i)
501 sbt_field_title[i] = sbt_field_title[i-1];
502 sbt_field_size[i] = sbt_field_size[i-1];
503 sbt_field[i] = sbt_field[i-1];
505 sbt_field_title[1] = strzone("|");
506 sbt_field[1] = SP_SEPARATOR;
507 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
509 LOG_INFO("fixed missing field '|'\n");
512 else if(!have_separator)
514 strunzone(sbt_field_title[sbt_num_fields]);
515 sbt_field_title[sbt_num_fields] = strzone("|");
516 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
517 sbt_field[sbt_num_fields] = SP_SEPARATOR;
519 LOG_INFO("fixed missing field '|'\n");
523 strunzone(sbt_field_title[sbt_num_fields]);
524 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
525 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
526 sbt_field[sbt_num_fields] = ps_secondary;
528 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
532 strunzone(sbt_field_title[sbt_num_fields]);
533 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
534 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
535 sbt_field[sbt_num_fields] = ps_primary;
537 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
541 sbt_field[sbt_num_fields] = SP_END;
545 vector sbt_field_rgb;
546 string sbt_field_icon0;
547 string sbt_field_icon1;
548 string sbt_field_icon2;
549 vector sbt_field_icon0_rgb;
550 vector sbt_field_icon1_rgb;
551 vector sbt_field_icon2_rgb;
552 float sbt_field_icon0_alpha;
553 float sbt_field_icon1_alpha;
554 float sbt_field_icon2_alpha;
555 string Scoreboard_GetField(entity pl, PlayerScoreField field)
557 float tmp, num, denom;
560 sbt_field_rgb = '1 1 1';
561 sbt_field_icon0 = "";
562 sbt_field_icon1 = "";
563 sbt_field_icon2 = "";
564 sbt_field_icon0_rgb = '1 1 1';
565 sbt_field_icon1_rgb = '1 1 1';
566 sbt_field_icon2_rgb = '1 1 1';
567 sbt_field_icon0_alpha = 1;
568 sbt_field_icon1_alpha = 1;
569 sbt_field_icon2_alpha = 1;
574 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
575 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
579 tmp = max(0, min(220, f-80)) / 220;
580 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
586 f = pl.ping_packetloss;
587 tmp = pl.ping_movementloss;
588 if(f == 0 && tmp == 0)
590 str = ftos(ceil(f * 100));
592 str = strcat(str, "~", ftos(ceil(tmp * 100)));
593 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
594 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
598 if(ready_waiting && pl.ready)
600 sbt_field_icon0 = "gfx/scoreboard/player_ready";
604 f = entcs_GetClientColors(pl.sv_entnum);
606 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
607 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
608 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
609 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
610 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
613 return entcs_GetName(pl.sv_entnum);
616 f = pl.(scores(SP_KILLS));
617 f -= pl.(scores(SP_SUICIDES));
621 num = pl.(scores(SP_KILLS));
622 denom = pl.(scores(SP_DEATHS));
625 sbt_field_rgb = '0 1 0';
626 str = sprintf("%d", num);
627 } else if(num <= 0) {
628 sbt_field_rgb = '1 0 0';
629 str = sprintf("%.1f", num/denom);
631 str = sprintf("%.1f", num/denom);
635 f = pl.(scores(SP_KILLS));
636 f -= pl.(scores(SP_DEATHS));
639 sbt_field_rgb = '0 1 0';
641 sbt_field_rgb = '1 1 1';
643 sbt_field_rgb = '1 0 0';
649 float elo = pl.(scores(SP_ELO));
651 case -1: return "...";
652 case -2: return _("N/A");
653 default: return ftos(elo);
658 num = pl.(scores(SP_DMG));
661 str = sprintf("%.1f k", num/denom);
665 num = pl.(scores(SP_DMGTAKEN));
668 str = sprintf("%.1f k", num/denom);
672 tmp = pl.(scores(field));
673 f = scores_flags(field);
674 if(field == ps_primary)
675 sbt_field_rgb = '1 1 0';
676 else if(field == ps_secondary)
677 sbt_field_rgb = '0 1 1';
679 sbt_field_rgb = '1 1 1';
680 return ScoreString(f, tmp);
685 float sbt_fixcolumnwidth_len;
686 float sbt_fixcolumnwidth_iconlen;
687 float sbt_fixcolumnwidth_marginlen;
689 string Scoreboard_FixColumnWidth(int i, string str)
694 PlayerScoreField field = sbt_field[i];
696 sbt_fixcolumnwidth_iconlen = 0;
698 if(sbt_field_icon0 != "")
700 sz = draw_getimagesize(sbt_field_icon0);
702 if(sbt_fixcolumnwidth_iconlen < f)
703 sbt_fixcolumnwidth_iconlen = f;
706 if(sbt_field_icon1 != "")
708 sz = draw_getimagesize(sbt_field_icon1);
710 if(sbt_fixcolumnwidth_iconlen < f)
711 sbt_fixcolumnwidth_iconlen = f;
714 if(sbt_field_icon2 != "")
716 sz = draw_getimagesize(sbt_field_icon2);
718 if(sbt_fixcolumnwidth_iconlen < f)
719 sbt_fixcolumnwidth_iconlen = f;
722 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
724 if(sbt_fixcolumnwidth_iconlen != 0)
725 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
727 sbt_fixcolumnwidth_marginlen = 0;
729 if(field == SP_NAME) // name gets all remaining space
733 namesize = panel_size.x;
734 for(j = 0; j < sbt_num_fields; ++j)
736 if (sbt_field[i] != SP_SEPARATOR)
737 namesize -= sbt_field_size[j] + hud_fontsize.x;
738 sbt_field_size[i] = namesize;
740 if (sbt_fixcolumnwidth_iconlen != 0)
741 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
742 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
743 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
746 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
748 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
749 if(sbt_field_size[i] < f)
750 sbt_field_size[i] = f;
755 vector Scoreboard_DrawHeader(vector pos, vector rgb)
758 vector column_dim = eY * panel_size.y;
759 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
760 pos.x += hud_fontsize.x * 0.5;
761 for(i = 0; i < sbt_num_fields; ++i)
763 if(sbt_field[i] == SP_SEPARATOR)
765 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
768 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
769 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
770 pos.x += column_dim.x;
772 if(sbt_field[i] == SP_SEPARATOR)
774 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
775 for(i = sbt_num_fields - 1; i > 0; --i)
777 if(sbt_field[i] == SP_SEPARATOR)
780 pos.x -= sbt_field_size[i];
785 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
786 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
789 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
790 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
791 pos.x -= hud_fontsize.x;
796 pos.y += 1.25 * hud_fontsize.y;
800 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
802 TC(bool, is_self); TC(int, pl_number);
804 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
806 vector h_pos = item_pos;
807 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
808 // alternated rows highlighting
810 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
811 else if((sbt_highlight) && (!(pl_number % 2)))
812 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
814 vector pos = item_pos;
815 pos.x += hud_fontsize.x * 0.5;
816 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
817 vector tmp = '0 0 0';
819 PlayerScoreField field;
820 for(i = 0; i < sbt_num_fields; ++i)
822 field = sbt_field[i];
823 if(field == SP_SEPARATOR)
826 if(is_spec && field != SP_NAME && field != SP_PING) {
827 pos.x += sbt_field_size[i] + hud_fontsize.x;
830 str = Scoreboard_GetField(pl, field);
831 str = Scoreboard_FixColumnWidth(i, str);
833 pos.x += sbt_field_size[i] + hud_fontsize.x;
835 if(field == SP_NAME) {
836 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
838 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
840 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
842 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
844 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
846 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
849 tmp.x = sbt_field_size[i] + hud_fontsize.x;
850 if(sbt_field_icon0 != "")
852 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);
854 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);
855 if(sbt_field_icon1 != "")
857 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);
859 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);
860 if(sbt_field_icon2 != "")
862 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);
864 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);
867 if(sbt_field[i] == SP_SEPARATOR)
869 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
870 for(i = sbt_num_fields-1; i > 0; --i)
872 field = sbt_field[i];
873 if(field == SP_SEPARATOR)
876 if(is_spec && field != SP_NAME && field != SP_PING) {
877 pos.x -= sbt_field_size[i] + hud_fontsize.x;
881 str = Scoreboard_GetField(pl, field);
882 str = Scoreboard_FixColumnWidth(i, str);
884 if(field == SP_NAME) {
885 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
887 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
889 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
891 tmp.x = sbt_fixcolumnwidth_len;
893 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
895 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_field_size[i];
899 if(sbt_field_icon0 != "")
901 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);
903 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);
904 if(sbt_field_icon1 != "")
906 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);
908 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);
909 if(sbt_field_icon2 != "")
911 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);
913 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);
914 pos.x -= sbt_field_size[i] + hud_fontsize.x;
919 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
922 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
927 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
928 panel_size.y += panel_bg_padding * 2;
929 HUD_Panel_DrawBg(scoreboard_fade_alpha);
931 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
932 if(panel.current_panel_bg != "0")
933 end_pos.y += panel_bg_border * 2;
937 panel_pos += '1 1 0' * panel_bg_padding;
938 panel_size -= '2 2 0' * panel_bg_padding;
942 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
946 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
948 pos.y += 1.25 * hud_fontsize.y;
951 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
953 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
956 // print header row and highlight columns
957 pos = Scoreboard_DrawHeader(panel_pos, rgb);
959 // fill the table and draw the rows
962 for(pl = players.sort_next; pl; pl = pl.sort_next)
964 if(pl.team != tm.team)
966 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
967 pos.y += 1.25 * hud_fontsize.y;
971 for(pl = players.sort_next; pl; pl = pl.sort_next)
973 if(pl.team == NUM_SPECTATOR)
975 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
976 pos.y += 1.25 * hud_fontsize.y;
980 panel_size.x += panel_bg_padding * 2; // restore initial width
984 float Scoreboard_WouldDraw() {
985 if (QuickMenu_IsOpened())
987 else if (HUD_Radar_Clickable())
989 else if (scoreboard_showscores)
991 else if (intermission == 1)
993 else if (intermission == 2)
995 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
997 else if (scoreboard_showscores_force)
1002 float average_accuracy;
1003 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1005 WepSet weapons_stat = WepSet_GetFromStat();
1006 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1007 int disownedcnt = 0;
1008 FOREACH(Weapons, it != WEP_Null, {
1009 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1011 WepSet set = it.m_wepset;
1012 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1016 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1017 if (weapon_cnt <= 0) return pos;
1020 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1022 int columnns = ceil(weapon_cnt / rows);
1026 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1027 pos.y += 1.25 * hud_fontsize.y;
1028 if(panel.current_panel_bg != "0")
1029 pos.y += panel_bg_border;
1032 panel_size.y = height * rows;
1033 panel_size.y += panel_bg_padding * 2;
1034 HUD_Panel_DrawBg(scoreboard_fade_alpha);
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 = panel_size;
1049 float fontsize = height * 1/3;
1050 float weapon_height = height * 2/3;
1051 float weapon_width = tmp.x / columnns / rows;
1054 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1058 // column highlighting
1059 for (int i = 0; i < columnns; ++i)
1061 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1064 for (int i = 0; i < rows; ++i)
1065 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1068 average_accuracy = 0;
1069 int weapons_with_stats = 0;
1071 pos.x += weapon_width / 2;
1073 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1076 Accuracy_LoadColors();
1078 float oldposx = pos.x;
1082 FOREACH(Weapons, it != WEP_Null, {
1083 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1085 WepSet set = it.m_wepset;
1086 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1090 if (weapon_stats >= 0)
1091 weapon_alpha = sbt_fg_alpha;
1093 weapon_alpha = 0.2 * sbt_fg_alpha;
1096 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1098 if (weapon_stats >= 0) {
1099 weapons_with_stats += 1;
1100 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1103 s = sprintf("%d%%", weapon_stats * 100);
1106 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1108 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1109 rgb = Accuracy_GetColor(weapon_stats);
1111 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1113 tmpos.x += weapon_width * rows;
1114 pos.x += weapon_width * rows;
1115 if (rows == 2 && column == columnns - 1) {
1123 if (weapons_with_stats)
1124 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1126 panel_size.x += panel_bg_padding * 2; // restore initial width
1130 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1132 pos.x += hud_fontsize.x * 0.25;
1133 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1134 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1135 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1137 pos.y += hud_fontsize.y;
1142 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1143 float stat_secrets_found, stat_secrets_total;
1144 float stat_monsters_killed, stat_monsters_total;
1148 // get monster stats
1149 stat_monsters_killed = STAT(MONSTERS_KILLED);
1150 stat_monsters_total = STAT(MONSTERS_TOTAL);
1152 // get secrets stats
1153 stat_secrets_found = STAT(SECRETS_FOUND);
1154 stat_secrets_total = STAT(SECRETS_TOTAL);
1156 // get number of rows
1157 if(stat_secrets_total)
1159 if(stat_monsters_total)
1162 // if no rows, return
1166 // draw table header
1167 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1168 pos.y += 1.25 * hud_fontsize.y;
1169 if(panel.current_panel_bg != "0")
1170 pos.y += panel_bg_border;
1173 panel_size.y = hud_fontsize.y * rows;
1174 panel_size.y += panel_bg_padding * 2;
1175 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1177 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1178 if(panel.current_panel_bg != "0")
1179 end_pos.y += panel_bg_border * 2;
1181 if(panel_bg_padding)
1183 panel_pos += '1 1 0' * panel_bg_padding;
1184 panel_size -= '2 2 0' * panel_bg_padding;
1188 vector tmp = panel_size;
1191 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1194 if(stat_monsters_total)
1196 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1197 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1201 if(stat_secrets_total)
1203 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1204 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1207 panel_size.x += panel_bg_padding * 2; // restore initial width
1212 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1215 RANKINGS_RECEIVED_CNT = 0;
1216 for (i=RANKINGS_CNT-1; i>=0; --i)
1218 ++RANKINGS_RECEIVED_CNT;
1220 if (RANKINGS_RECEIVED_CNT == 0)
1223 vector hl_rgb = rgb + '0.5 0.5 0.5';
1225 pos.y += hud_fontsize.y;
1226 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1227 pos.y += 1.25 * hud_fontsize.y;
1228 if(panel.current_panel_bg != "0")
1229 pos.y += panel_bg_border;
1232 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1233 panel_size.y += panel_bg_padding * 2;
1234 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1236 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1237 if(panel.current_panel_bg != "0")
1238 end_pos.y += panel_bg_border * 2;
1240 if(panel_bg_padding)
1242 panel_pos += '1 1 0' * panel_bg_padding;
1243 panel_size -= '2 2 0' * panel_bg_padding;
1247 vector tmp = panel_size;
1250 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1253 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1260 n = grecordholder[i];
1261 p = count_ordinal(i+1);
1262 if(grecordholder[i] == entcs_GetName(player_localnum))
1263 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1264 else if(!(i % 2) && sbt_highlight)
1265 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1266 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1267 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);
1268 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1269 pos.y += 1.25 * hud_fontsize.y;
1272 panel_size.x += panel_bg_padding * 2; // restore initial width
1276 void Scoreboard_Draw()
1278 if(!autocvar__hud_configure)
1280 // frametime checks allow to toggle the scoreboard even when the game is paused
1281 if(scoreboard_active) {
1282 if(hud_configure_menu_open == 1)
1283 scoreboard_fade_alpha = 1;
1284 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1285 if (scoreboard_fadeinspeed && frametime)
1286 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1288 scoreboard_fade_alpha = 1;
1291 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1292 if (scoreboard_fadeoutspeed && frametime)
1293 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1295 scoreboard_fade_alpha = 0;
1298 if (!scoreboard_fade_alpha)
1302 scoreboard_fade_alpha = 0;
1304 if (autocvar_hud_panel_scoreboard_dynamichud)
1307 HUD_Scale_Disable();
1309 float hud_fade_alpha_save = hud_fade_alpha;
1310 if(hud_configure_menu_open == 1)
1313 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1314 HUD_Panel_UpdateCvars();
1316 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1317 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1318 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1319 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1320 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1321 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1323 hud_fade_alpha = hud_fade_alpha_save;
1325 // don't overlap with con_notify
1326 if(!autocvar__hud_configure)
1327 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1329 Scoreboard_UpdatePlayerTeams();
1335 // Initializes position
1339 vector sb_heading_fontsize;
1340 sb_heading_fontsize = hud_fontsize * 2;
1341 draw_beginBoldFont();
1342 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1345 pos.y += sb_heading_fontsize.y;
1346 if(panel.current_panel_bg != "0")
1347 pos.y += panel_bg_border;
1349 // Draw the scoreboard
1350 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1353 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1357 vector panel_bg_color_save = panel_bg_color;
1358 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1359 if(panel.current_panel_bg != "0")
1360 team_score_baseoffset.x -= panel_bg_border;
1361 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1363 if(tm.team == NUM_SPECTATOR)
1368 draw_beginBoldFont();
1369 vector rgb = Team_ColorRGB(tm.team);
1370 str = ftos(tm.(teamscores(ts_primary)));
1371 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1373 if(ts_primary != ts_secondary)
1375 str = ftos(tm.(teamscores(ts_secondary)));
1376 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);
1379 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1380 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1381 else if(panel_bg_color_team > 0)
1382 panel_bg_color = rgb * panel_bg_color_team;
1384 panel_bg_color = rgb;
1385 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1387 panel_bg_color = panel_bg_color_save;
1391 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1393 if(tm.team == NUM_SPECTATOR)
1396 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1400 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1401 if(race_speedaward) {
1402 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);
1403 pos.y += 1.25 * hud_fontsize.y;
1405 if(race_speedaward_alltimebest) {
1406 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);
1407 pos.y += 1.25 * hud_fontsize.y;
1409 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1411 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1412 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1414 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1419 for(pl = players.sort_next; pl; pl = pl.sort_next)
1421 if(pl.team != NUM_SPECTATOR)
1423 pos.y += 1.25 * hud_fontsize.y;
1424 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1430 draw_beginBoldFont();
1431 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1433 pos.y += 1.25 * hud_fontsize.y;
1436 // Print info string
1438 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1439 tl = STAT(TIMELIMIT);
1440 fl = STAT(FRAGLIMIT);
1441 ll = STAT(LEADLIMIT);
1442 if(gametype == MAPINFO_TYPE_LMS)
1445 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1450 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1454 str = strcat(str, _(" or"));
1457 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1458 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1459 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1460 TranslateScoresLabel(teamscores_label(ts_primary))));
1464 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1465 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1466 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1467 TranslateScoresLabel(scores_label(ps_primary))));
1472 if(tl > 0 || fl > 0)
1473 str = strcat(str, _(" or"));
1476 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1477 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1478 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1479 TranslateScoresLabel(teamscores_label(ts_primary))));
1483 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1484 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1485 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1486 TranslateScoresLabel(scores_label(ps_primary))));
1491 pos.y += 1.2 * hud_fontsize.y;
1492 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1494 // print information about respawn status
1495 float respawn_time = STAT(RESPAWN_TIME);
1499 if(respawn_time < 0)
1501 // a negative number means we are awaiting respawn, time value is still the same
1502 respawn_time *= -1; // remove mark now that we checked it
1504 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1505 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1507 str = sprintf(_("^1Respawning in ^3%s^1..."),
1508 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1509 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1511 count_seconds(ceil(respawn_time - time))
1515 else if(time < respawn_time)
1517 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1518 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1519 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1521 count_seconds(ceil(respawn_time - time))
1525 else if(time >= respawn_time)
1526 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1528 pos.y += 1.2 * hud_fontsize.y;
1529 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1532 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;