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^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 ps_secondary = SP_SCORE;
367 scores_label(ps_primary) = strzone("score");
368 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
371 // TODO: re enable with gametype dependant cvars?
372 if(argc < 3) // no arguments provided
373 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
376 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
380 if(argv(2) == "default")
381 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
382 else if(argv(2) == "all")
385 s = "ping pl name |";
386 FOREACH(Scores, true, {
388 if(it != ps_secondary)
389 if(scores_label(it) != "")
390 s = strcat(s, " ", scores_label(it));
392 if(ps_secondary != ps_primary)
393 s = strcat(s, " ", scores_label(ps_secondary));
394 s = strcat(s, " ", scores_label(ps_primary));
395 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
402 hud_fontsize = HUD_GetFontsize("hud_fontsize");
404 for(i = 1; i < argc - 1; ++i)
410 if(substring(str, 0, 1) == "?")
413 str = substring(str, 1, strlen(str) - 1);
416 slash = strstrofs(str, "/", 0);
419 pattern = substring(str, 0, slash);
420 str = substring(str, slash + 1, strlen(str) - (slash + 1));
422 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
426 strunzone(sbt_field_title[sbt_num_fields]);
427 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
428 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
429 str = strtolower(str);
434 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
435 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
436 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
437 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
438 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
439 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
440 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
441 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
442 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
445 FOREACH(Scores, true, {
446 if (str == strtolower(scores_label(it))) {
448 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
458 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
462 sbt_field[sbt_num_fields] = j;
465 if(j == ps_secondary)
471 if(sbt_num_fields >= MAX_SBT_FIELDS)
475 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
477 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
479 if(ps_primary == ps_secondary)
481 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
483 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
487 strunzone(sbt_field_title[sbt_num_fields]);
488 for(i = sbt_num_fields; i > 0; --i)
490 sbt_field_title[i] = sbt_field_title[i-1];
491 sbt_field_size[i] = sbt_field_size[i-1];
492 sbt_field[i] = sbt_field[i-1];
494 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
495 sbt_field[0] = SP_NAME;
497 LOG_INFO("fixed missing field 'name'\n");
501 strunzone(sbt_field_title[sbt_num_fields]);
502 for(i = sbt_num_fields; i > 1; --i)
504 sbt_field_title[i] = sbt_field_title[i-1];
505 sbt_field_size[i] = sbt_field_size[i-1];
506 sbt_field[i] = sbt_field[i-1];
508 sbt_field_title[1] = strzone("|");
509 sbt_field[1] = SP_SEPARATOR;
510 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
512 LOG_INFO("fixed missing field '|'\n");
515 else if(!have_separator)
517 strunzone(sbt_field_title[sbt_num_fields]);
518 sbt_field_title[sbt_num_fields] = strzone("|");
519 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
520 sbt_field[sbt_num_fields] = SP_SEPARATOR;
522 LOG_INFO("fixed missing field '|'\n");
526 strunzone(sbt_field_title[sbt_num_fields]);
527 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
528 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
529 sbt_field[sbt_num_fields] = ps_secondary;
531 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
535 strunzone(sbt_field_title[sbt_num_fields]);
536 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
537 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
538 sbt_field[sbt_num_fields] = ps_primary;
540 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
544 sbt_field[sbt_num_fields] = SP_END;
548 vector sbt_field_rgb;
549 string sbt_field_icon0;
550 string sbt_field_icon1;
551 string sbt_field_icon2;
552 vector sbt_field_icon0_rgb;
553 vector sbt_field_icon1_rgb;
554 vector sbt_field_icon2_rgb;
555 float sbt_field_icon0_alpha;
556 float sbt_field_icon1_alpha;
557 float sbt_field_icon2_alpha;
558 string Scoreboard_GetField(entity pl, PlayerScoreField field)
560 float tmp, num, denom;
563 sbt_field_rgb = '1 1 1';
564 sbt_field_icon0 = "";
565 sbt_field_icon1 = "";
566 sbt_field_icon2 = "";
567 sbt_field_icon0_rgb = '1 1 1';
568 sbt_field_icon1_rgb = '1 1 1';
569 sbt_field_icon2_rgb = '1 1 1';
570 sbt_field_icon0_alpha = 1;
571 sbt_field_icon1_alpha = 1;
572 sbt_field_icon2_alpha = 1;
577 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
578 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
582 tmp = max(0, min(220, f-80)) / 220;
583 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
589 f = pl.ping_packetloss;
590 tmp = pl.ping_movementloss;
591 if(f == 0 && tmp == 0)
593 str = ftos(ceil(f * 100));
595 str = strcat(str, "~", ftos(ceil(tmp * 100)));
596 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
597 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
601 if(ready_waiting && pl.ready)
603 sbt_field_icon0 = "gfx/scoreboard/player_ready";
607 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
609 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
610 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
611 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
612 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
613 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
616 return entcs_GetName(pl.sv_entnum);
619 f = pl.(scores(SP_KILLS));
620 f -= pl.(scores(SP_SUICIDES));
624 num = pl.(scores(SP_KILLS));
625 denom = pl.(scores(SP_DEATHS));
628 sbt_field_rgb = '0 1 0';
629 str = sprintf("%d", num);
630 } else if(num <= 0) {
631 sbt_field_rgb = '1 0 0';
632 str = sprintf("%.1f", num/denom);
634 str = sprintf("%.1f", num/denom);
638 f = pl.(scores(SP_KILLS));
639 f -= pl.(scores(SP_DEATHS));
642 sbt_field_rgb = '0 1 0';
644 sbt_field_rgb = '1 1 1';
646 sbt_field_rgb = '1 0 0';
652 float elo = pl.(scores(SP_ELO));
654 case -1: return "...";
655 case -2: return _("N/A");
656 default: return ftos(elo);
661 num = pl.(scores(SP_DMG));
664 str = sprintf("%.1f k", num/denom);
668 num = pl.(scores(SP_DMGTAKEN));
671 str = sprintf("%.1f k", num/denom);
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)
697 PlayerScoreField field = sbt_field[i];
699 sbt_fixcolumnwidth_iconlen = 0;
701 if(sbt_field_icon0 != "")
703 sz = draw_getimagesize(sbt_field_icon0);
705 if(sbt_fixcolumnwidth_iconlen < f)
706 sbt_fixcolumnwidth_iconlen = f;
709 if(sbt_field_icon1 != "")
711 sz = draw_getimagesize(sbt_field_icon1);
713 if(sbt_fixcolumnwidth_iconlen < f)
714 sbt_fixcolumnwidth_iconlen = f;
717 if(sbt_field_icon2 != "")
719 sz = draw_getimagesize(sbt_field_icon2);
721 if(sbt_fixcolumnwidth_iconlen < f)
722 sbt_fixcolumnwidth_iconlen = f;
725 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
727 if(sbt_fixcolumnwidth_iconlen != 0)
728 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
730 sbt_fixcolumnwidth_marginlen = 0;
732 if(field == SP_NAME) // name gets all remaining space
736 namesize = panel_size.x;
737 for(j = 0; j < sbt_num_fields; ++j)
739 if (sbt_field[i] != SP_SEPARATOR)
740 namesize -= sbt_field_size[j] + hud_fontsize.x;
741 sbt_field_size[i] = namesize;
743 if (sbt_fixcolumnwidth_iconlen != 0)
744 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
745 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
746 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
749 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
751 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
752 if(sbt_field_size[i] < f)
753 sbt_field_size[i] = f;
758 vector Scoreboard_DrawHeader(vector pos, vector rgb)
761 vector column_dim = eY * panel_size.y;
762 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
763 pos.x += hud_fontsize.x * 0.5;
764 for(i = 0; i < sbt_num_fields; ++i)
766 if(sbt_field[i] == SP_SEPARATOR)
768 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
771 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
772 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
773 pos.x += column_dim.x;
775 if(sbt_field[i] == SP_SEPARATOR)
777 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
778 for(i = sbt_num_fields - 1; i > 0; --i)
780 if(sbt_field[i] == SP_SEPARATOR)
783 pos.x -= sbt_field_size[i];
788 if (i == sbt_num_fields-1)
789 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
791 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
792 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
795 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
796 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
797 pos.x -= hud_fontsize.x;
802 pos.y += 1.25 * hud_fontsize.y;
806 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
808 TC(bool, is_self); TC(int, pl_number);
810 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
811 if(is_spec && !is_self)
814 vector h_pos = item_pos;
815 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
816 // alternated rows highlighting
818 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
819 else if((sbt_highlight) && (!(pl_number % 2)))
820 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
822 vector pos = item_pos;
823 pos.x += hud_fontsize.x * 0.5;
824 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
825 vector tmp = '0 0 0';
827 PlayerScoreField field;
828 for(i = 0; i < sbt_num_fields; ++i)
830 field = sbt_field[i];
831 if(field == SP_SEPARATOR)
834 if(is_spec && field != SP_NAME && field != SP_PING) {
835 pos.x += sbt_field_size[i] + hud_fontsize.x;
838 str = Scoreboard_GetField(pl, field);
839 str = Scoreboard_FixColumnWidth(i, str);
841 pos.x += sbt_field_size[i] + hud_fontsize.x;
843 if(field == SP_NAME) {
844 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
846 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
848 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
850 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
852 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
854 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
857 tmp.x = sbt_field_size[i] + hud_fontsize.x;
858 if(sbt_field_icon0 != "")
860 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);
862 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);
863 if(sbt_field_icon1 != "")
865 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);
867 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);
868 if(sbt_field_icon2 != "")
870 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);
872 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);
875 if(sbt_field[i] == SP_SEPARATOR)
877 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
878 for(i = sbt_num_fields-1; i > 0; --i)
880 field = sbt_field[i];
881 if(field == SP_SEPARATOR)
884 if(is_spec && field != SP_NAME && field != SP_PING) {
885 pos.x -= sbt_field_size[i] + hud_fontsize.x;
889 str = Scoreboard_GetField(pl, field);
890 str = Scoreboard_FixColumnWidth(i, str);
892 if(field == SP_NAME) {
893 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
895 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
897 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
899 tmp.x = sbt_fixcolumnwidth_len;
901 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
903 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
906 tmp.x = sbt_field_size[i];
907 if(sbt_field_icon0 != "")
909 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);
911 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);
912 if(sbt_field_icon1 != "")
914 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);
916 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);
917 if(sbt_field_icon2 != "")
919 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);
921 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);
922 pos.x -= sbt_field_size[i] + hud_fontsize.x;
927 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
930 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
935 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
936 panel_size.y += panel_bg_padding * 2;
937 HUD_Panel_DrawBg(scoreboard_fade_alpha);
939 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
940 if(panel.current_panel_bg != "0")
941 end_pos.y += panel_bg_border * 2;
945 panel_pos += '1 1 0' * panel_bg_padding;
946 panel_size -= '2 2 0' * panel_bg_padding;
950 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
954 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
956 pos.y += 1.25 * hud_fontsize.y;
959 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
961 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
964 // print header row and highlight columns
965 pos = Scoreboard_DrawHeader(panel_pos, rgb);
967 // fill the table and draw the rows
970 for(pl = players.sort_next; pl; pl = pl.sort_next)
972 if(pl.team != tm.team)
974 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
975 pos.y += 1.25 * hud_fontsize.y;
979 for(pl = players.sort_next; pl; pl = pl.sort_next)
981 if(pl.team == NUM_SPECTATOR)
983 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
984 pos.y += 1.25 * hud_fontsize.y;
988 panel_size.x += panel_bg_padding * 2; // restore initial width
992 float Scoreboard_WouldDraw() {
993 if (QuickMenu_IsOpened())
995 else if (HUD_Radar_Clickable())
997 else if (scoreboard_showscores)
999 else if (intermission == 1)
1001 else if (intermission == 2)
1003 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1005 else if (scoreboard_showscores_force)
1010 float average_accuracy;
1011 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1013 WepSet weapons_stat = WepSet_GetFromStat();
1014 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1015 int disownedcnt = 0;
1016 FOREACH(Weapons, it != WEP_Null, {
1017 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1019 WepSet set = it.m_wepset;
1020 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1024 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1025 if (weapon_cnt <= 0) return pos;
1028 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1030 int columnns = ceil(weapon_cnt / rows);
1034 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1035 pos.y += 1.25 * hud_fontsize.y;
1036 if(panel.current_panel_bg != "0")
1037 pos.y += panel_bg_border;
1040 panel_size.y = height * rows;
1041 panel_size.y += panel_bg_padding * 2;
1042 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1044 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1045 if(panel.current_panel_bg != "0")
1046 end_pos.y += panel_bg_border * 2;
1048 if(panel_bg_padding)
1050 panel_pos += '1 1 0' * panel_bg_padding;
1051 panel_size -= '2 2 0' * panel_bg_padding;
1055 vector tmp = panel_size;
1057 float fontsize = height * 1/3;
1058 float weapon_height = height * 2/3;
1059 float weapon_width = tmp.x / columnns / rows;
1062 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1066 // column highlighting
1067 for (int i = 0; i < columnns; ++i)
1069 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1072 for (int i = 0; i < rows; ++i)
1073 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1076 average_accuracy = 0;
1077 int weapons_with_stats = 0;
1079 pos.x += weapon_width / 2;
1081 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1084 Accuracy_LoadColors();
1086 float oldposx = pos.x;
1090 FOREACH(Weapons, it != WEP_Null, {
1091 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1093 WepSet set = it.m_wepset;
1094 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1098 if (weapon_stats >= 0)
1099 weapon_alpha = sbt_fg_alpha;
1101 weapon_alpha = 0.2 * sbt_fg_alpha;
1104 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1106 if (weapon_stats >= 0) {
1107 weapons_with_stats += 1;
1108 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1111 s = sprintf("%d%%", weapon_stats * 100);
1114 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1116 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1117 rgb = Accuracy_GetColor(weapon_stats);
1119 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1121 tmpos.x += weapon_width * rows;
1122 pos.x += weapon_width * rows;
1123 if (rows == 2 && column == columnns - 1) {
1131 if (weapons_with_stats)
1132 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1134 panel_size.x += panel_bg_padding * 2; // restore initial width
1138 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1140 pos.x += hud_fontsize.x * 0.25;
1141 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1142 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1143 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1145 pos.y += hud_fontsize.y;
1150 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1151 float stat_secrets_found, stat_secrets_total;
1152 float stat_monsters_killed, stat_monsters_total;
1156 // get monster stats
1157 stat_monsters_killed = STAT(MONSTERS_KILLED);
1158 stat_monsters_total = STAT(MONSTERS_TOTAL);
1160 // get secrets stats
1161 stat_secrets_found = STAT(SECRETS_FOUND);
1162 stat_secrets_total = STAT(SECRETS_TOTAL);
1164 // get number of rows
1165 if(stat_secrets_total)
1167 if(stat_monsters_total)
1170 // if no rows, return
1174 // draw table header
1175 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1176 pos.y += 1.25 * hud_fontsize.y;
1177 if(panel.current_panel_bg != "0")
1178 pos.y += panel_bg_border;
1181 panel_size.y = hud_fontsize.y * rows;
1182 panel_size.y += panel_bg_padding * 2;
1183 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1185 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1186 if(panel.current_panel_bg != "0")
1187 end_pos.y += panel_bg_border * 2;
1189 if(panel_bg_padding)
1191 panel_pos += '1 1 0' * panel_bg_padding;
1192 panel_size -= '2 2 0' * panel_bg_padding;
1196 vector tmp = panel_size;
1199 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1202 if(stat_monsters_total)
1204 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1205 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1209 if(stat_secrets_total)
1211 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1212 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1215 panel_size.x += panel_bg_padding * 2; // restore initial width
1220 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1223 RANKINGS_RECEIVED_CNT = 0;
1224 for (i=RANKINGS_CNT-1; i>=0; --i)
1226 ++RANKINGS_RECEIVED_CNT;
1228 if (RANKINGS_RECEIVED_CNT == 0)
1231 vector hl_rgb = rgb + '0.5 0.5 0.5';
1233 pos.y += hud_fontsize.y;
1234 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1235 pos.y += 1.25 * hud_fontsize.y;
1236 if(panel.current_panel_bg != "0")
1237 pos.y += panel_bg_border;
1240 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1241 panel_size.y += panel_bg_padding * 2;
1242 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1244 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1245 if(panel.current_panel_bg != "0")
1246 end_pos.y += panel_bg_border * 2;
1248 if(panel_bg_padding)
1250 panel_pos += '1 1 0' * panel_bg_padding;
1251 panel_size -= '2 2 0' * panel_bg_padding;
1255 vector tmp = panel_size;
1258 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1261 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1268 n = grecordholder[i];
1269 p = count_ordinal(i+1);
1270 if(grecordholder[i] == entcs_GetName(player_localnum))
1271 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1272 else if(!(i % 2) && sbt_highlight)
1273 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1274 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1275 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);
1276 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1277 pos.y += 1.25 * hud_fontsize.y;
1280 panel_size.x += panel_bg_padding * 2; // restore initial width
1284 void Scoreboard_Draw()
1286 if(!autocvar__hud_configure)
1288 // frametime checks allow to toggle the scoreboard even when the game is paused
1289 if(scoreboard_active) {
1290 if(menu_enabled == 1)
1291 scoreboard_fade_alpha = 1;
1292 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1293 if (scoreboard_fadeinspeed && frametime)
1294 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1296 scoreboard_fade_alpha = 1;
1299 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1300 if (scoreboard_fadeoutspeed && frametime)
1301 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1303 scoreboard_fade_alpha = 0;
1306 if (!scoreboard_fade_alpha)
1310 scoreboard_fade_alpha = 0;
1312 if (autocvar_hud_panel_scoreboard_dynamichud)
1315 HUD_Scale_Disable();
1317 float hud_fade_alpha_save = hud_fade_alpha;
1318 if(menu_enabled == 1)
1321 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1322 HUD_Panel_UpdateCvars();
1324 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1325 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1326 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1327 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1328 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1329 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1331 hud_fade_alpha = hud_fade_alpha_save;
1333 // don't overlap with con_notify
1334 if(!autocvar__hud_configure)
1335 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1337 Scoreboard_UpdatePlayerTeams();
1343 // Initializes position
1347 vector sb_heading_fontsize;
1348 sb_heading_fontsize = hud_fontsize * 2;
1349 draw_beginBoldFont();
1350 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1353 pos.y += sb_heading_fontsize.y;
1354 if(panel.current_panel_bg != "0")
1355 pos.y += panel_bg_border;
1357 // Draw the scoreboard
1358 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1361 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1365 vector panel_bg_color_save = panel_bg_color;
1366 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1367 if(panel.current_panel_bg != "0")
1368 team_score_baseoffset.x -= panel_bg_border;
1369 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1371 if(tm.team == NUM_SPECTATOR)
1373 if(!tm.team && teamplay)
1376 draw_beginBoldFont();
1377 vector rgb = Team_ColorRGB(tm.team);
1378 str = ftos(tm.(teamscores(ts_primary)));
1379 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1381 if(ts_primary != ts_secondary)
1383 str = ftos(tm.(teamscores(ts_secondary)));
1384 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);
1387 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1388 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1389 else if(panel_bg_color_team > 0)
1390 panel_bg_color = rgb * panel_bg_color_team;
1392 panel_bg_color = rgb;
1393 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1395 panel_bg_color = panel_bg_color_save;
1399 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1401 if(tm.team == NUM_SPECTATOR)
1403 if(!tm.team && teamplay)
1406 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1410 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1411 if(race_speedaward) {
1412 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);
1413 pos.y += 1.25 * hud_fontsize.y;
1415 if(race_speedaward_alltimebest) {
1416 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);
1417 pos.y += 1.25 * hud_fontsize.y;
1419 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1421 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1422 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1424 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1429 for(pl = players.sort_next; pl; pl = pl.sort_next)
1431 if(pl.team != NUM_SPECTATOR)
1433 pos.y += 1.25 * hud_fontsize.y;
1434 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1440 draw_beginBoldFont();
1441 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1443 pos.y += 1.25 * hud_fontsize.y;
1446 // Print info string
1448 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1449 tl = STAT(TIMELIMIT);
1450 fl = STAT(FRAGLIMIT);
1451 ll = STAT(LEADLIMIT);
1452 if(gametype == MAPINFO_TYPE_LMS)
1455 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1460 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1464 str = strcat(str, _(" or"));
1467 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1468 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1469 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1470 TranslateScoresLabel(teamscores_label(ts_primary))));
1474 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1475 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1476 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1477 TranslateScoresLabel(scores_label(ps_primary))));
1482 if(tl > 0 || fl > 0)
1483 str = strcat(str, _(" or"));
1486 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1487 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1488 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1489 TranslateScoresLabel(teamscores_label(ts_primary))));
1493 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1494 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1495 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1496 TranslateScoresLabel(scores_label(ps_primary))));
1501 pos.y += 1.2 * hud_fontsize.y;
1502 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1504 // print information about respawn status
1505 float respawn_time = STAT(RESPAWN_TIME);
1509 if(respawn_time < 0)
1511 // a negative number means we are awaiting respawn, time value is still the same
1512 respawn_time *= -1; // remove mark now that we checked it
1514 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1515 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1517 str = sprintf(_("^1Respawning in ^3%s^1..."),
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)
1527 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1528 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1529 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1531 count_seconds(ceil(respawn_time - time))
1535 else if(time >= respawn_time)
1536 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1538 pos.y += 1.2 * hud_fontsize.y;
1539 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1542 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;