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 "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 "rounds": return CTX(_("SCO^rounds won"));
89 case "score": return CTX(_("SCO^score"));
90 case "suicides": return CTX(_("SCO^suicides"));
91 case "takes": return CTX(_("SCO^takes"));
92 case "ticks": return CTX(_("SCO^ticks"));
97 void Scoreboard_InitScores()
101 ps_primary = ps_secondary = NULL;
102 ts_primary = ts_secondary = -1;
103 FOREACH(Scores, true, {
104 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
105 if(f == SFL_SORT_PRIO_PRIMARY)
107 if(f == SFL_SORT_PRIO_SECONDARY)
110 if(ps_secondary == NULL)
111 ps_secondary = ps_primary;
113 for(i = 0; i < MAX_TEAMSCORE; ++i)
115 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
116 if(f == SFL_SORT_PRIO_PRIMARY)
118 if(f == SFL_SORT_PRIO_SECONDARY)
121 if(ts_secondary == -1)
122 ts_secondary = ts_primary;
124 Cmd_Scoreboard_SetFields(0);
127 float SetTeam(entity pl, float Team);
129 void Scoreboard_UpdatePlayerTeams()
136 for(pl = players.sort_next; pl; pl = pl.sort_next)
139 Team = entcs_GetScoreTeam(pl.sv_entnum);
140 if(SetTeam(pl, Team))
143 Scoreboard_UpdatePlayerPos(pl);
147 pl = players.sort_next;
152 print(strcat("PNUM: ", ftos(num), "\n"));
157 int Scoreboard_CompareScore(int vl, int vr, int f)
159 TC(int, vl); TC(int, vr); TC(int, f);
160 if(f & SFL_ZERO_IS_WORST)
162 if(vl == 0 && vr != 0)
164 if(vl != 0 && vr == 0)
168 return IS_INCREASING(f);
170 return IS_DECREASING(f);
174 float Scoreboard_ComparePlayerScores(entity left, entity right)
177 vl = entcs_GetTeam(left.sv_entnum);
178 vr = entcs_GetTeam(right.sv_entnum);
190 if(vl == NUM_SPECTATOR)
192 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
194 if(!left.gotscores && right.gotscores)
199 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
203 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
207 FOREACH(Scores, true, {
208 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
209 if (r >= 0) return r;
212 if (left.sv_entnum < right.sv_entnum)
218 void Scoreboard_UpdatePlayerPos(entity player)
221 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
223 SORT_SWAP(player, ent);
225 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
227 SORT_SWAP(ent, player);
231 float Scoreboard_CompareTeamScores(entity left, entity right)
235 if(left.team == NUM_SPECTATOR)
237 if(right.team == NUM_SPECTATOR)
240 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
244 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
248 for(i = 0; i < MAX_TEAMSCORE; ++i)
250 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
255 if (left.team < right.team)
261 void Scoreboard_UpdateTeamPos(entity Team)
264 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
266 SORT_SWAP(Team, ent);
268 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
270 SORT_SWAP(ent, Team);
274 void Cmd_Scoreboard_Help()
276 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
277 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
278 LOG_INFO(_("Usage:\n"));
279 LOG_INFO(_("^2scoreboard_columns_set default\n"));
280 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
281 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
282 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
285 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
286 LOG_INFO(_("^3ping^7 Ping time\n"));
287 LOG_INFO(_("^3pl^7 Packet loss\n"));
288 LOG_INFO(_("^3elo^7 Player ELO\n"));
289 LOG_INFO(_("^3kills^7 Number of kills\n"));
290 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
291 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
292 LOG_INFO(_("^3frags^7 kills - suicides\n"));
293 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
294 LOG_INFO(_("^3dmg^7 The total damage done\n"));
295 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
296 LOG_INFO(_("^3sum^7 frags - deaths\n"));
297 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
298 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
299 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
300 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
301 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
302 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
303 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
304 LOG_INFO(_("^3rank^7 Player rank\n"));
305 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
306 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
307 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
308 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
309 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
310 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
311 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
312 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
313 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
314 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
315 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
316 LOG_INFO(_("^3score^7 Total score\n"));
319 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
320 "of game types, then a slash, to make the field show up only in these\n"
321 "or in all but these game types. You can also specify 'all' as a\n"
322 "field to show all fields available for the current game mode.\n\n"));
324 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
325 "include/exclude ALL teams/noteams game modes.\n\n"));
327 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
328 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
329 "right of the vertical bar aligned to the right.\n"));
330 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
331 "other gamemodes except DM.\n"));
334 // NOTE: adding a gametype with ? to not warn for an optional field
335 // make sure it's excluded in a previous exclusive rule, if any
336 // otherwise the previous exclusive rule warns anyway
337 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
338 #define SCOREBOARD_DEFAULT_COLUMNS \
340 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
341 " -teams,lms/deaths +ft,tdm/deaths" \
342 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
343 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
344 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
345 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
346 " +lms/lives +lms/rank" \
347 " +kh/caps +kh/pushes +kh/destroyed" \
348 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
349 " +as/objectives +nb/faults +nb/goals" \
350 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
351 " -lms,rc,cts,inv,nb/score"
353 void Cmd_Scoreboard_SetFields(int argc)
358 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
362 return; // do nothing, we don't know gametype and scores yet
364 // sbt_fields uses strunzone on the titles!
365 if(!sbt_field_title[0])
366 for(i = 0; i < MAX_SBT_FIELDS; ++i)
367 sbt_field_title[i] = strzone("(null)");
369 // TODO: re enable with gametype dependant cvars?
370 if(argc < 3) // no arguments provided
371 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
374 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
378 if(argv(2) == "default")
379 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
380 else if(argv(2) == "all")
383 s = "ping pl name |";
384 FOREACH(Scores, true, {
386 if(it != ps_secondary)
387 if(scores_label(it) != "")
388 s = strcat(s, " ", scores_label(it));
390 if(ps_secondary != ps_primary)
391 s = strcat(s, " ", scores_label(ps_secondary));
392 s = strcat(s, " ", scores_label(ps_primary));
393 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
400 hud_fontsize = HUD_GetFontsize("hud_fontsize");
402 for(i = 1; i < argc - 1; ++i)
408 if(substring(str, 0, 1) == "?")
411 str = substring(str, 1, strlen(str) - 1);
414 slash = strstrofs(str, "/", 0);
417 pattern = substring(str, 0, slash);
418 str = substring(str, slash + 1, strlen(str) - (slash + 1));
420 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
424 strunzone(sbt_field_title[sbt_num_fields]);
425 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
426 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
427 str = strtolower(str);
432 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
433 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
434 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
435 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
436 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
437 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
438 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
439 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
440 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
443 FOREACH(Scores, true, {
444 if (str == strtolower(scores_label(it))) {
446 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
456 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
460 sbt_field[sbt_num_fields] = j;
463 if(j == ps_secondary)
469 if(sbt_num_fields >= MAX_SBT_FIELDS)
473 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
475 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
477 if(ps_primary == ps_secondary)
479 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
481 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
485 strunzone(sbt_field_title[sbt_num_fields]);
486 for(i = sbt_num_fields; i > 0; --i)
488 sbt_field_title[i] = sbt_field_title[i-1];
489 sbt_field_size[i] = sbt_field_size[i-1];
490 sbt_field[i] = sbt_field[i-1];
492 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
493 sbt_field[0] = SP_NAME;
495 LOG_INFO("fixed missing field 'name'\n");
499 strunzone(sbt_field_title[sbt_num_fields]);
500 for(i = sbt_num_fields; i > 1; --i)
502 sbt_field_title[i] = sbt_field_title[i-1];
503 sbt_field_size[i] = sbt_field_size[i-1];
504 sbt_field[i] = sbt_field[i-1];
506 sbt_field_title[1] = strzone("|");
507 sbt_field[1] = SP_SEPARATOR;
508 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
510 LOG_INFO("fixed missing field '|'\n");
513 else if(!have_separator)
515 strunzone(sbt_field_title[sbt_num_fields]);
516 sbt_field_title[sbt_num_fields] = strzone("|");
517 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
518 sbt_field[sbt_num_fields] = SP_SEPARATOR;
520 LOG_INFO("fixed missing field '|'\n");
524 strunzone(sbt_field_title[sbt_num_fields]);
525 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
526 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
527 sbt_field[sbt_num_fields] = ps_secondary;
529 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
533 strunzone(sbt_field_title[sbt_num_fields]);
534 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
535 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
536 sbt_field[sbt_num_fields] = ps_primary;
538 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
542 sbt_field[sbt_num_fields] = SP_END;
546 vector sbt_field_rgb;
547 string sbt_field_icon0;
548 string sbt_field_icon1;
549 string sbt_field_icon2;
550 vector sbt_field_icon0_rgb;
551 vector sbt_field_icon1_rgb;
552 vector sbt_field_icon2_rgb;
553 float sbt_field_icon0_alpha;
554 float sbt_field_icon1_alpha;
555 float sbt_field_icon2_alpha;
556 string Scoreboard_GetField(entity pl, PlayerScoreField field)
558 float tmp, num, denom;
561 sbt_field_rgb = '1 1 1';
562 sbt_field_icon0 = "";
563 sbt_field_icon1 = "";
564 sbt_field_icon2 = "";
565 sbt_field_icon0_rgb = '1 1 1';
566 sbt_field_icon1_rgb = '1 1 1';
567 sbt_field_icon2_rgb = '1 1 1';
568 sbt_field_icon0_alpha = 1;
569 sbt_field_icon1_alpha = 1;
570 sbt_field_icon2_alpha = 1;
575 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
576 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
580 tmp = max(0, min(220, f-80)) / 220;
581 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
587 f = pl.ping_packetloss;
588 tmp = pl.ping_movementloss;
589 if(f == 0 && tmp == 0)
591 str = ftos(ceil(f * 100));
593 str = strcat(str, "~", ftos(ceil(tmp * 100)));
594 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
595 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
599 if(ready_waiting && pl.ready)
601 sbt_field_icon0 = "gfx/scoreboard/player_ready";
605 f = entcs_GetClientColors(pl.sv_entnum);
607 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
608 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
609 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
610 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
611 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
614 return entcs_GetName(pl.sv_entnum);
617 f = pl.(scores(SP_KILLS));
618 f -= pl.(scores(SP_SUICIDES));
622 num = pl.(scores(SP_KILLS));
623 denom = pl.(scores(SP_DEATHS));
626 sbt_field_rgb = '0 1 0';
627 str = sprintf("%d", num);
628 } else if(num <= 0) {
629 sbt_field_rgb = '1 0 0';
630 str = sprintf("%.1f", num/denom);
632 str = sprintf("%.1f", num/denom);
636 f = pl.(scores(SP_KILLS));
637 f -= pl.(scores(SP_DEATHS));
640 sbt_field_rgb = '0 1 0';
642 sbt_field_rgb = '1 1 1';
644 sbt_field_rgb = '1 0 0';
650 float elo = pl.(scores(SP_ELO));
652 case -1: return "...";
653 case -2: return _("N/A");
654 default: return ftos(elo);
659 num = pl.(scores(SP_DMG));
662 str = sprintf("%.1f k", num/denom);
666 num = pl.(scores(SP_DMGTAKEN));
669 str = sprintf("%.1f k", num/denom);
673 tmp = pl.(scores(field));
674 f = scores_flags(field);
675 if(field == ps_primary)
676 sbt_field_rgb = '1 1 0';
677 else if(field == ps_secondary)
678 sbt_field_rgb = '0 1 1';
680 sbt_field_rgb = '1 1 1';
681 return ScoreString(f, tmp);
686 float sbt_fixcolumnwidth_len;
687 float sbt_fixcolumnwidth_iconlen;
688 float sbt_fixcolumnwidth_marginlen;
690 string Scoreboard_FixColumnWidth(int i, string str)
695 PlayerScoreField field = sbt_field[i];
697 sbt_fixcolumnwidth_iconlen = 0;
699 if(sbt_field_icon0 != "")
701 sz = draw_getimagesize(sbt_field_icon0);
703 if(sbt_fixcolumnwidth_iconlen < f)
704 sbt_fixcolumnwidth_iconlen = f;
707 if(sbt_field_icon1 != "")
709 sz = draw_getimagesize(sbt_field_icon1);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 if(sbt_field_icon2 != "")
717 sz = draw_getimagesize(sbt_field_icon2);
719 if(sbt_fixcolumnwidth_iconlen < f)
720 sbt_fixcolumnwidth_iconlen = f;
723 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
725 if(sbt_fixcolumnwidth_iconlen != 0)
726 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
728 sbt_fixcolumnwidth_marginlen = 0;
730 if(field == SP_NAME) // name gets all remaining space
734 namesize = panel_size.x;
735 for(j = 0; j < sbt_num_fields; ++j)
737 if (sbt_field[i] != SP_SEPARATOR)
738 namesize -= sbt_field_size[j] + hud_fontsize.x;
739 sbt_field_size[i] = namesize;
741 if (sbt_fixcolumnwidth_iconlen != 0)
742 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
743 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
744 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
747 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
749 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
750 if(sbt_field_size[i] < f)
751 sbt_field_size[i] = f;
756 vector Scoreboard_DrawHeader(vector pos, vector rgb)
759 vector column_dim = eY * panel_size.y;
760 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
761 pos.x += hud_fontsize.x * 0.5;
762 for(i = 0; i < sbt_num_fields; ++i)
764 if(sbt_field[i] == SP_SEPARATOR)
766 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
769 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
770 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
771 pos.x += column_dim.x;
773 if(sbt_field[i] == SP_SEPARATOR)
775 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
776 for(i = sbt_num_fields - 1; i > 0; --i)
778 if(sbt_field[i] == SP_SEPARATOR)
781 pos.x -= sbt_field_size[i];
786 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
787 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
790 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
791 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
792 pos.x -= hud_fontsize.x;
797 pos.y += 1.25 * hud_fontsize.y;
801 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
803 TC(bool, is_self); TC(int, pl_number);
805 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
807 vector h_pos = item_pos;
808 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
809 // alternated rows highlighting
811 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
812 else if((sbt_highlight) && (!(pl_number % 2)))
813 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
815 vector pos = item_pos;
816 pos.x += hud_fontsize.x * 0.5;
817 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
818 vector tmp = '0 0 0';
820 PlayerScoreField field;
821 for(i = 0; i < sbt_num_fields; ++i)
823 field = sbt_field[i];
824 if(field == SP_SEPARATOR)
827 if(is_spec && field != SP_NAME && field != SP_PING) {
828 pos.x += sbt_field_size[i] + hud_fontsize.x;
831 str = Scoreboard_GetField(pl, field);
832 str = Scoreboard_FixColumnWidth(i, str);
834 pos.x += sbt_field_size[i] + hud_fontsize.x;
836 if(field == SP_NAME) {
837 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
839 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
841 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
843 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
845 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
847 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
850 tmp.x = sbt_field_size[i] + hud_fontsize.x;
851 if(sbt_field_icon0 != "")
853 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);
855 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);
856 if(sbt_field_icon1 != "")
858 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);
860 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);
861 if(sbt_field_icon2 != "")
863 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);
865 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);
868 if(sbt_field[i] == SP_SEPARATOR)
870 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
871 for(i = sbt_num_fields-1; i > 0; --i)
873 field = sbt_field[i];
874 if(field == SP_SEPARATOR)
877 if(is_spec && field != SP_NAME && field != SP_PING) {
878 pos.x -= sbt_field_size[i] + hud_fontsize.x;
882 str = Scoreboard_GetField(pl, field);
883 str = Scoreboard_FixColumnWidth(i, str);
885 if(field == SP_NAME) {
886 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
888 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
890 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
892 tmp.x = sbt_fixcolumnwidth_len;
894 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
896 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
899 tmp.x = sbt_field_size[i];
900 if(sbt_field_icon0 != "")
902 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);
904 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);
905 if(sbt_field_icon1 != "")
907 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);
909 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);
910 if(sbt_field_icon2 != "")
912 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);
914 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);
915 pos.x -= sbt_field_size[i] + hud_fontsize.x;
920 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
923 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
928 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
929 panel_size.y += panel_bg_padding * 2;
930 HUD_Panel_DrawBg(scoreboard_fade_alpha);
932 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
933 if(panel.current_panel_bg != "0")
934 end_pos.y += panel_bg_border * 2;
938 panel_pos += '1 1 0' * panel_bg_padding;
939 panel_size -= '2 2 0' * panel_bg_padding;
943 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
947 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
949 pos.y += 1.25 * hud_fontsize.y;
952 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
954 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
957 // print header row and highlight columns
958 pos = Scoreboard_DrawHeader(panel_pos, rgb);
960 // fill the table and draw the rows
963 for(pl = players.sort_next; pl; pl = pl.sort_next)
965 if(pl.team != tm.team)
967 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
968 pos.y += 1.25 * hud_fontsize.y;
972 for(pl = players.sort_next; pl; pl = pl.sort_next)
974 if(pl.team == NUM_SPECTATOR)
976 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
977 pos.y += 1.25 * hud_fontsize.y;
981 panel_size.x += panel_bg_padding * 2; // restore initial width
985 float Scoreboard_WouldDraw() {
986 if (QuickMenu_IsOpened())
988 else if (HUD_Radar_Clickable())
990 else if (scoreboard_showscores)
992 else if (intermission == 1)
994 else if (intermission == 2)
996 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
998 else if (scoreboard_showscores_force)
1003 float average_accuracy;
1004 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1006 WepSet weapons_stat = WepSet_GetFromStat();
1007 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1008 int disownedcnt = 0;
1009 FOREACH(Weapons, it != WEP_Null, {
1010 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1012 WepSet set = it.m_wepset;
1013 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1017 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1018 if (weapon_cnt <= 0) return pos;
1021 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1023 int columnns = ceil(weapon_cnt / rows);
1027 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1028 pos.y += 1.25 * hud_fontsize.y;
1029 if(panel.current_panel_bg != "0")
1030 pos.y += panel_bg_border;
1033 panel_size.y = height * rows;
1034 panel_size.y += panel_bg_padding * 2;
1035 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1037 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1038 if(panel.current_panel_bg != "0")
1039 end_pos.y += panel_bg_border * 2;
1041 if(panel_bg_padding)
1043 panel_pos += '1 1 0' * panel_bg_padding;
1044 panel_size -= '2 2 0' * panel_bg_padding;
1048 vector tmp = panel_size;
1050 float fontsize = height * 1/3;
1051 float weapon_height = height * 2/3;
1052 float weapon_width = tmp.x / columnns / rows;
1055 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1059 // column highlighting
1060 for (int i = 0; i < columnns; ++i)
1062 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1065 for (int i = 0; i < rows; ++i)
1066 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1069 average_accuracy = 0;
1070 int weapons_with_stats = 0;
1072 pos.x += weapon_width / 2;
1074 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1077 Accuracy_LoadColors();
1079 float oldposx = pos.x;
1083 FOREACH(Weapons, it != WEP_Null, {
1084 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1086 WepSet set = it.m_wepset;
1087 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1091 if (weapon_stats >= 0)
1092 weapon_alpha = sbt_fg_alpha;
1094 weapon_alpha = 0.2 * sbt_fg_alpha;
1097 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1099 if (weapon_stats >= 0) {
1100 weapons_with_stats += 1;
1101 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1104 s = sprintf("%d%%", weapon_stats * 100);
1107 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1109 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1110 rgb = Accuracy_GetColor(weapon_stats);
1112 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1114 tmpos.x += weapon_width * rows;
1115 pos.x += weapon_width * rows;
1116 if (rows == 2 && column == columnns - 1) {
1124 if (weapons_with_stats)
1125 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1127 panel_size.x += panel_bg_padding * 2; // restore initial width
1131 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1133 pos.x += hud_fontsize.x * 0.25;
1134 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1135 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1136 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1138 pos.y += hud_fontsize.y;
1143 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1144 float stat_secrets_found, stat_secrets_total;
1145 float stat_monsters_killed, stat_monsters_total;
1149 // get monster stats
1150 stat_monsters_killed = STAT(MONSTERS_KILLED);
1151 stat_monsters_total = STAT(MONSTERS_TOTAL);
1153 // get secrets stats
1154 stat_secrets_found = STAT(SECRETS_FOUND);
1155 stat_secrets_total = STAT(SECRETS_TOTAL);
1157 // get number of rows
1158 if(stat_secrets_total)
1160 if(stat_monsters_total)
1163 // if no rows, return
1167 // draw table header
1168 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1169 pos.y += 1.25 * hud_fontsize.y;
1170 if(panel.current_panel_bg != "0")
1171 pos.y += panel_bg_border;
1174 panel_size.y = hud_fontsize.y * rows;
1175 panel_size.y += panel_bg_padding * 2;
1176 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1178 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1179 if(panel.current_panel_bg != "0")
1180 end_pos.y += panel_bg_border * 2;
1182 if(panel_bg_padding)
1184 panel_pos += '1 1 0' * panel_bg_padding;
1185 panel_size -= '2 2 0' * panel_bg_padding;
1189 vector tmp = panel_size;
1192 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1195 if(stat_monsters_total)
1197 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1198 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1202 if(stat_secrets_total)
1204 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1205 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1208 panel_size.x += panel_bg_padding * 2; // restore initial width
1213 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1216 RANKINGS_RECEIVED_CNT = 0;
1217 for (i=RANKINGS_CNT-1; i>=0; --i)
1219 ++RANKINGS_RECEIVED_CNT;
1221 if (RANKINGS_RECEIVED_CNT == 0)
1224 vector hl_rgb = rgb + '0.5 0.5 0.5';
1226 pos.y += hud_fontsize.y;
1227 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1228 pos.y += 1.25 * hud_fontsize.y;
1229 if(panel.current_panel_bg != "0")
1230 pos.y += panel_bg_border;
1233 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1234 panel_size.y += panel_bg_padding * 2;
1235 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1237 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1238 if(panel.current_panel_bg != "0")
1239 end_pos.y += panel_bg_border * 2;
1241 if(panel_bg_padding)
1243 panel_pos += '1 1 0' * panel_bg_padding;
1244 panel_size -= '2 2 0' * panel_bg_padding;
1248 vector tmp = panel_size;
1251 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1254 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1261 n = grecordholder[i];
1262 p = count_ordinal(i+1);
1263 if(grecordholder[i] == entcs_GetName(player_localnum))
1264 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1265 else if(!(i % 2) && sbt_highlight)
1266 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1267 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1268 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);
1269 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1270 pos.y += 1.25 * hud_fontsize.y;
1273 panel_size.x += panel_bg_padding * 2; // restore initial width
1277 void Scoreboard_Draw()
1279 if(!autocvar__hud_configure)
1281 // frametime checks allow to toggle the scoreboard even when the game is paused
1282 if(scoreboard_active) {
1283 if(hud_configure_menu_open == 1)
1284 scoreboard_fade_alpha = 1;
1285 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1286 if (scoreboard_fadeinspeed && frametime)
1287 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1289 scoreboard_fade_alpha = 1;
1292 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1293 if (scoreboard_fadeoutspeed && frametime)
1294 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1296 scoreboard_fade_alpha = 0;
1299 if (!scoreboard_fade_alpha)
1303 scoreboard_fade_alpha = 0;
1305 if (autocvar_hud_panel_scoreboard_dynamichud)
1308 HUD_Scale_Disable();
1310 float hud_fade_alpha_save = hud_fade_alpha;
1311 if(hud_configure_menu_open == 1)
1314 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1315 HUD_Panel_UpdateCvars();
1317 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1318 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1319 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1320 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1321 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1322 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1324 hud_fade_alpha = hud_fade_alpha_save;
1326 // don't overlap with con_notify
1327 if(!autocvar__hud_configure)
1328 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1330 Scoreboard_UpdatePlayerTeams();
1336 // Initializes position
1340 vector sb_heading_fontsize;
1341 sb_heading_fontsize = hud_fontsize * 2;
1342 draw_beginBoldFont();
1343 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1346 pos.y += sb_heading_fontsize.y;
1347 if(panel.current_panel_bg != "0")
1348 pos.y += panel_bg_border;
1350 // Draw the scoreboard
1351 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1354 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1358 vector panel_bg_color_save = panel_bg_color;
1359 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1360 if(panel.current_panel_bg != "0")
1361 team_score_baseoffset.x -= panel_bg_border;
1362 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1364 if(tm.team == NUM_SPECTATOR)
1369 draw_beginBoldFont();
1370 vector rgb = Team_ColorRGB(tm.team);
1371 str = ftos(tm.(teamscores(ts_primary)));
1372 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1374 if(ts_primary != ts_secondary)
1376 str = ftos(tm.(teamscores(ts_secondary)));
1377 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);
1380 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1381 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1382 else if(panel_bg_color_team > 0)
1383 panel_bg_color = rgb * panel_bg_color_team;
1385 panel_bg_color = rgb;
1386 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1388 panel_bg_color = panel_bg_color_save;
1392 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1394 if(tm.team == NUM_SPECTATOR)
1397 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1401 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1402 if(race_speedaward) {
1403 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);
1404 pos.y += 1.25 * hud_fontsize.y;
1406 if(race_speedaward_alltimebest) {
1407 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);
1408 pos.y += 1.25 * hud_fontsize.y;
1410 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1412 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1413 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1415 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1420 for(pl = players.sort_next; pl; pl = pl.sort_next)
1422 if(pl.team != NUM_SPECTATOR)
1424 pos.y += 1.25 * hud_fontsize.y;
1425 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1431 draw_beginBoldFont();
1432 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1434 pos.y += 1.25 * hud_fontsize.y;
1437 // Print info string
1439 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1440 tl = STAT(TIMELIMIT);
1441 fl = STAT(FRAGLIMIT);
1442 ll = STAT(LEADLIMIT);
1443 if(gametype == MAPINFO_TYPE_LMS)
1446 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1451 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1455 str = strcat(str, _(" or"));
1458 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1459 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1460 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1461 TranslateScoresLabel(teamscores_label(ts_primary))));
1465 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1466 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1467 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1468 TranslateScoresLabel(scores_label(ps_primary))));
1473 if(tl > 0 || fl > 0)
1474 str = strcat(str, _(" or"));
1477 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1478 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1479 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1480 TranslateScoresLabel(teamscores_label(ts_primary))));
1484 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1485 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1486 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1487 TranslateScoresLabel(scores_label(ps_primary))));
1492 pos.y += 1.2 * hud_fontsize.y;
1493 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1495 // print information about respawn status
1496 float respawn_time = STAT(RESPAWN_TIME);
1500 if(respawn_time < 0)
1502 // a negative number means we are awaiting respawn, time value is still the same
1503 respawn_time *= -1; // remove mark now that we checked it
1505 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1506 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1508 str = sprintf(_("^1Respawning in ^3%s^1..."),
1509 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1510 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1512 count_seconds(ceil(respawn_time - time))
1516 else if(time < respawn_time)
1518 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1519 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1520 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1522 count_seconds(ceil(respawn_time - time))
1526 else if(time >= respawn_time)
1527 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1529 pos.y += 1.2 * hud_fontsize.y;
1530 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1533 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;