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 "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;
363 // set up a temporary scoreboard layout
364 // no layout can be properly set up until score_info data haven't been received
365 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
366 ps_primary = SP_SCORE;
367 ps_secondary = SP_SCORE;
368 scores_label(ps_primary) = strzone("score");
369 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
372 // TODO: re enable with gametype dependant cvars?
373 if(argc < 3) // no arguments provided
374 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
377 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
381 if(argv(2) == "default")
382 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
383 else if(argv(2) == "all")
386 s = "ping pl name |";
387 FOREACH(Scores, true, {
389 if(it != ps_secondary)
390 if(scores_label(it) != "")
391 s = strcat(s, " ", scores_label(it));
393 if(ps_secondary != ps_primary)
394 s = strcat(s, " ", scores_label(ps_secondary));
395 s = strcat(s, " ", scores_label(ps_primary));
396 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
403 hud_fontsize = HUD_GetFontsize("hud_fontsize");
405 for(i = 1; i < argc - 1; ++i)
411 if(substring(str, 0, 1) == "?")
414 str = substring(str, 1, strlen(str) - 1);
417 slash = strstrofs(str, "/", 0);
420 pattern = substring(str, 0, slash);
421 str = substring(str, slash + 1, strlen(str) - (slash + 1));
423 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
427 strunzone(sbt_field_title[sbt_num_fields]);
428 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
429 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
430 str = strtolower(str);
435 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
436 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
437 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
438 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
439 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
440 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
441 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
442 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
443 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
446 FOREACH(Scores, true, {
447 if (str == strtolower(scores_label(it))) {
449 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
459 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
463 sbt_field[sbt_num_fields] = j;
466 if(j == ps_secondary)
472 if(sbt_num_fields >= MAX_SBT_FIELDS)
476 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
478 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
480 if(ps_primary == ps_secondary)
482 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
484 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
488 strunzone(sbt_field_title[sbt_num_fields]);
489 for(i = sbt_num_fields; i > 0; --i)
491 sbt_field_title[i] = sbt_field_title[i-1];
492 sbt_field_size[i] = sbt_field_size[i-1];
493 sbt_field[i] = sbt_field[i-1];
495 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
496 sbt_field[0] = SP_NAME;
498 LOG_INFO("fixed missing field 'name'\n");
502 strunzone(sbt_field_title[sbt_num_fields]);
503 for(i = sbt_num_fields; i > 1; --i)
505 sbt_field_title[i] = sbt_field_title[i-1];
506 sbt_field_size[i] = sbt_field_size[i-1];
507 sbt_field[i] = sbt_field[i-1];
509 sbt_field_title[1] = strzone("|");
510 sbt_field[1] = SP_SEPARATOR;
511 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
513 LOG_INFO("fixed missing field '|'\n");
516 else if(!have_separator)
518 strunzone(sbt_field_title[sbt_num_fields]);
519 sbt_field_title[sbt_num_fields] = strzone("|");
520 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
521 sbt_field[sbt_num_fields] = SP_SEPARATOR;
523 LOG_INFO("fixed missing field '|'\n");
527 strunzone(sbt_field_title[sbt_num_fields]);
528 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
529 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
530 sbt_field[sbt_num_fields] = ps_secondary;
532 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
536 strunzone(sbt_field_title[sbt_num_fields]);
537 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
538 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
539 sbt_field[sbt_num_fields] = ps_primary;
541 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
545 sbt_field[sbt_num_fields] = SP_END;
549 vector sbt_field_rgb;
550 string sbt_field_icon0;
551 string sbt_field_icon1;
552 string sbt_field_icon2;
553 vector sbt_field_icon0_rgb;
554 vector sbt_field_icon1_rgb;
555 vector sbt_field_icon2_rgb;
556 float sbt_field_icon0_alpha;
557 float sbt_field_icon1_alpha;
558 float sbt_field_icon2_alpha;
559 string Scoreboard_GetField(entity pl, PlayerScoreField field)
561 float tmp, num, denom;
564 sbt_field_rgb = '1 1 1';
565 sbt_field_icon0 = "";
566 sbt_field_icon1 = "";
567 sbt_field_icon2 = "";
568 sbt_field_icon0_rgb = '1 1 1';
569 sbt_field_icon1_rgb = '1 1 1';
570 sbt_field_icon2_rgb = '1 1 1';
571 sbt_field_icon0_alpha = 1;
572 sbt_field_icon1_alpha = 1;
573 sbt_field_icon2_alpha = 1;
578 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
579 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
583 tmp = max(0, min(220, f-80)) / 220;
584 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
590 f = pl.ping_packetloss;
591 tmp = pl.ping_movementloss;
592 if(f == 0 && tmp == 0)
594 str = ftos(ceil(f * 100));
596 str = strcat(str, "~", ftos(ceil(tmp * 100)));
597 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
598 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
602 if(ready_waiting && pl.ready)
604 sbt_field_icon0 = "gfx/scoreboard/player_ready";
608 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
610 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
611 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
612 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
613 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
614 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617 return entcs_GetName(pl.sv_entnum);
620 f = pl.(scores(SP_KILLS));
621 f -= pl.(scores(SP_SUICIDES));
625 num = pl.(scores(SP_KILLS));
626 denom = pl.(scores(SP_DEATHS));
629 sbt_field_rgb = '0 1 0';
630 str = sprintf("%d", num);
631 } else if(num <= 0) {
632 sbt_field_rgb = '1 0 0';
633 str = sprintf("%.1f", num/denom);
635 str = sprintf("%.1f", num/denom);
639 f = pl.(scores(SP_KILLS));
640 f -= pl.(scores(SP_DEATHS));
643 sbt_field_rgb = '0 1 0';
645 sbt_field_rgb = '1 1 1';
647 sbt_field_rgb = '1 0 0';
653 float elo = pl.(scores(SP_ELO));
655 case -1: return "...";
656 case -2: return _("N/A");
657 default: return ftos(elo);
662 num = pl.(scores(SP_DMG));
665 str = sprintf("%.1f k", num/denom);
669 num = pl.(scores(SP_DMGTAKEN));
672 str = sprintf("%.1f k", num/denom);
676 tmp = pl.(scores(field));
677 f = scores_flags(field);
678 if(field == ps_primary)
679 sbt_field_rgb = '1 1 0';
680 else if(field == ps_secondary)
681 sbt_field_rgb = '0 1 1';
683 sbt_field_rgb = '1 1 1';
684 return ScoreString(f, tmp);
689 float sbt_fixcolumnwidth_len;
690 float sbt_fixcolumnwidth_iconlen;
691 float sbt_fixcolumnwidth_marginlen;
693 string Scoreboard_FixColumnWidth(int i, string str)
698 PlayerScoreField field = sbt_field[i];
700 sbt_fixcolumnwidth_iconlen = 0;
702 if(sbt_field_icon0 != "")
704 sz = draw_getimagesize(sbt_field_icon0);
706 if(sbt_fixcolumnwidth_iconlen < f)
707 sbt_fixcolumnwidth_iconlen = f;
710 if(sbt_field_icon1 != "")
712 sz = draw_getimagesize(sbt_field_icon1);
714 if(sbt_fixcolumnwidth_iconlen < f)
715 sbt_fixcolumnwidth_iconlen = f;
718 if(sbt_field_icon2 != "")
720 sz = draw_getimagesize(sbt_field_icon2);
722 if(sbt_fixcolumnwidth_iconlen < f)
723 sbt_fixcolumnwidth_iconlen = f;
726 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
728 if(sbt_fixcolumnwidth_iconlen != 0)
729 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
731 sbt_fixcolumnwidth_marginlen = 0;
733 if(field == SP_NAME) // name gets all remaining space
737 namesize = panel_size.x;
738 for(j = 0; j < sbt_num_fields; ++j)
740 if (sbt_field[i] != SP_SEPARATOR)
741 namesize -= sbt_field_size[j] + hud_fontsize.x;
742 sbt_field_size[i] = namesize;
744 if (sbt_fixcolumnwidth_iconlen != 0)
745 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
746 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
747 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
750 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
752 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 if(sbt_field_size[i] < f)
754 sbt_field_size[i] = f;
759 vector Scoreboard_DrawHeader(vector pos, vector rgb)
762 vector column_dim = eY * panel_size.y;
763 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
764 pos.x += hud_fontsize.x * 0.5;
765 for(i = 0; i < sbt_num_fields; ++i)
767 if(sbt_field[i] == SP_SEPARATOR)
769 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
772 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
773 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
774 pos.x += column_dim.x;
776 if(sbt_field[i] == SP_SEPARATOR)
778 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
779 for(i = sbt_num_fields - 1; i > 0; --i)
781 if(sbt_field[i] == SP_SEPARATOR)
784 pos.x -= sbt_field_size[i];
789 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
790 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
793 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
794 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
795 pos.x -= hud_fontsize.x;
800 pos.y += 1.25 * hud_fontsize.y;
804 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
806 TC(bool, is_self); TC(int, pl_number);
808 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
810 vector h_pos = item_pos;
811 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
812 // alternated rows highlighting
814 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
815 else if((sbt_highlight) && (!(pl_number % 2)))
816 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
818 vector pos = item_pos;
819 pos.x += hud_fontsize.x * 0.5;
820 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
821 vector tmp = '0 0 0';
823 PlayerScoreField field;
824 for(i = 0; i < sbt_num_fields; ++i)
826 field = sbt_field[i];
827 if(field == SP_SEPARATOR)
830 if(is_spec && field != SP_NAME && field != SP_PING) {
831 pos.x += sbt_field_size[i] + hud_fontsize.x;
834 str = Scoreboard_GetField(pl, field);
835 str = Scoreboard_FixColumnWidth(i, str);
837 pos.x += sbt_field_size[i] + hud_fontsize.x;
839 if(field == SP_NAME) {
840 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
842 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
844 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
846 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
848 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
850 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_field_size[i] + hud_fontsize.x;
854 if(sbt_field_icon0 != "")
856 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);
858 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);
859 if(sbt_field_icon1 != "")
861 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);
863 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);
864 if(sbt_field_icon2 != "")
866 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha_self, DRAWFLAG_NORMAL);
868 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);
871 if(sbt_field[i] == SP_SEPARATOR)
873 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
874 for(i = sbt_num_fields-1; i > 0; --i)
876 field = sbt_field[i];
877 if(field == SP_SEPARATOR)
880 if(is_spec && field != SP_NAME && field != SP_PING) {
881 pos.x -= sbt_field_size[i] + hud_fontsize.x;
885 str = Scoreboard_GetField(pl, field);
886 str = Scoreboard_FixColumnWidth(i, str);
888 if(field == SP_NAME) {
889 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
891 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
893 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
895 tmp.x = sbt_fixcolumnwidth_len;
897 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
899 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_field_size[i];
903 if(sbt_field_icon0 != "")
905 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);
907 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);
908 if(sbt_field_icon1 != "")
910 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);
912 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);
913 if(sbt_field_icon2 != "")
915 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);
917 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);
918 pos.x -= sbt_field_size[i] + hud_fontsize.x;
923 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
926 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
931 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
932 panel_size.y += panel_bg_padding * 2;
933 HUD_Panel_DrawBg(scoreboard_fade_alpha);
935 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
936 if(panel.current_panel_bg != "0")
937 end_pos.y += panel_bg_border * 2;
941 panel_pos += '1 1 0' * panel_bg_padding;
942 panel_size -= '2 2 0' * panel_bg_padding;
946 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
950 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
952 pos.y += 1.25 * hud_fontsize.y;
955 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
957 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
960 // print header row and highlight columns
961 pos = Scoreboard_DrawHeader(panel_pos, rgb);
963 // fill the table and draw the rows
966 for(pl = players.sort_next; pl; pl = pl.sort_next)
968 if(pl.team != tm.team)
970 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
971 pos.y += 1.25 * hud_fontsize.y;
975 for(pl = players.sort_next; pl; pl = pl.sort_next)
977 if(pl.team == NUM_SPECTATOR)
979 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
980 pos.y += 1.25 * hud_fontsize.y;
984 panel_size.x += panel_bg_padding * 2; // restore initial width
988 float Scoreboard_WouldDraw() {
989 if (QuickMenu_IsOpened())
991 else if (HUD_Radar_Clickable())
993 else if (scoreboard_showscores)
995 else if (intermission == 1)
997 else if (intermission == 2)
999 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1001 else if (scoreboard_showscores_force)
1006 float average_accuracy;
1007 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1009 WepSet weapons_stat = WepSet_GetFromStat();
1010 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1011 int disownedcnt = 0;
1012 FOREACH(Weapons, it != WEP_Null, {
1013 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1015 WepSet set = it.m_wepset;
1016 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1020 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1021 if (weapon_cnt <= 0) return pos;
1024 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1026 int columnns = ceil(weapon_cnt / rows);
1030 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1031 pos.y += 1.25 * hud_fontsize.y;
1032 if(panel.current_panel_bg != "0")
1033 pos.y += panel_bg_border;
1036 panel_size.y = height * rows;
1037 panel_size.y += panel_bg_padding * 2;
1038 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1040 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1041 if(panel.current_panel_bg != "0")
1042 end_pos.y += panel_bg_border * 2;
1044 if(panel_bg_padding)
1046 panel_pos += '1 1 0' * panel_bg_padding;
1047 panel_size -= '2 2 0' * panel_bg_padding;
1051 vector tmp = panel_size;
1053 float fontsize = height * 1/3;
1054 float weapon_height = height * 2/3;
1055 float weapon_width = tmp.x / columnns / rows;
1058 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1062 // column highlighting
1063 for (int i = 0; i < columnns; ++i)
1065 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1068 for (int i = 0; i < rows; ++i)
1069 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1072 average_accuracy = 0;
1073 int weapons_with_stats = 0;
1075 pos.x += weapon_width / 2;
1077 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1080 Accuracy_LoadColors();
1082 float oldposx = pos.x;
1086 FOREACH(Weapons, it != WEP_Null, {
1087 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1089 WepSet set = it.m_wepset;
1090 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1094 if (weapon_stats >= 0)
1095 weapon_alpha = sbt_fg_alpha;
1097 weapon_alpha = 0.2 * sbt_fg_alpha;
1100 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1102 if (weapon_stats >= 0) {
1103 weapons_with_stats += 1;
1104 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1107 s = sprintf("%d%%", weapon_stats * 100);
1110 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1112 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1113 rgb = Accuracy_GetColor(weapon_stats);
1115 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1117 tmpos.x += weapon_width * rows;
1118 pos.x += weapon_width * rows;
1119 if (rows == 2 && column == columnns - 1) {
1127 if (weapons_with_stats)
1128 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1130 panel_size.x += panel_bg_padding * 2; // restore initial width
1134 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1136 pos.x += hud_fontsize.x * 0.25;
1137 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1138 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1139 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1141 pos.y += hud_fontsize.y;
1146 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1147 float stat_secrets_found, stat_secrets_total;
1148 float stat_monsters_killed, stat_monsters_total;
1152 // get monster stats
1153 stat_monsters_killed = STAT(MONSTERS_KILLED);
1154 stat_monsters_total = STAT(MONSTERS_TOTAL);
1156 // get secrets stats
1157 stat_secrets_found = STAT(SECRETS_FOUND);
1158 stat_secrets_total = STAT(SECRETS_TOTAL);
1160 // get number of rows
1161 if(stat_secrets_total)
1163 if(stat_monsters_total)
1166 // if no rows, return
1170 // draw table header
1171 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1172 pos.y += 1.25 * hud_fontsize.y;
1173 if(panel.current_panel_bg != "0")
1174 pos.y += panel_bg_border;
1177 panel_size.y = hud_fontsize.y * rows;
1178 panel_size.y += panel_bg_padding * 2;
1179 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1181 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1182 if(panel.current_panel_bg != "0")
1183 end_pos.y += panel_bg_border * 2;
1185 if(panel_bg_padding)
1187 panel_pos += '1 1 0' * panel_bg_padding;
1188 panel_size -= '2 2 0' * panel_bg_padding;
1192 vector tmp = panel_size;
1195 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1198 if(stat_monsters_total)
1200 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1201 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1205 if(stat_secrets_total)
1207 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1208 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1211 panel_size.x += panel_bg_padding * 2; // restore initial width
1216 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1219 RANKINGS_RECEIVED_CNT = 0;
1220 for (i=RANKINGS_CNT-1; i>=0; --i)
1222 ++RANKINGS_RECEIVED_CNT;
1224 if (RANKINGS_RECEIVED_CNT == 0)
1227 vector hl_rgb = rgb + '0.5 0.5 0.5';
1229 pos.y += hud_fontsize.y;
1230 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1231 pos.y += 1.25 * hud_fontsize.y;
1232 if(panel.current_panel_bg != "0")
1233 pos.y += panel_bg_border;
1236 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1237 panel_size.y += panel_bg_padding * 2;
1238 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1240 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1241 if(panel.current_panel_bg != "0")
1242 end_pos.y += panel_bg_border * 2;
1244 if(panel_bg_padding)
1246 panel_pos += '1 1 0' * panel_bg_padding;
1247 panel_size -= '2 2 0' * panel_bg_padding;
1251 vector tmp = panel_size;
1254 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1257 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1264 n = grecordholder[i];
1265 p = count_ordinal(i+1);
1266 if(grecordholder[i] == entcs_GetName(player_localnum))
1267 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1268 else if(!(i % 2) && sbt_highlight)
1269 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1270 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1271 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);
1272 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1273 pos.y += 1.25 * hud_fontsize.y;
1276 panel_size.x += panel_bg_padding * 2; // restore initial width
1280 void Scoreboard_Draw()
1282 if(!autocvar__hud_configure)
1284 // frametime checks allow to toggle the scoreboard even when the game is paused
1285 if(scoreboard_active) {
1286 if(hud_configure_menu_open == 1)
1287 scoreboard_fade_alpha = 1;
1288 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1289 if (scoreboard_fadeinspeed && frametime)
1290 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1292 scoreboard_fade_alpha = 1;
1295 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1296 if (scoreboard_fadeoutspeed && frametime)
1297 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1299 scoreboard_fade_alpha = 0;
1302 if (!scoreboard_fade_alpha)
1306 scoreboard_fade_alpha = 0;
1308 if (autocvar_hud_panel_scoreboard_dynamichud)
1311 HUD_Scale_Disable();
1313 float hud_fade_alpha_save = hud_fade_alpha;
1314 if(hud_configure_menu_open == 1)
1317 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1318 HUD_Panel_UpdateCvars();
1320 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1321 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1322 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1323 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1324 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1325 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1327 hud_fade_alpha = hud_fade_alpha_save;
1329 // don't overlap with con_notify
1330 if(!autocvar__hud_configure)
1331 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1333 Scoreboard_UpdatePlayerTeams();
1339 // Initializes position
1343 vector sb_heading_fontsize;
1344 sb_heading_fontsize = hud_fontsize * 2;
1345 draw_beginBoldFont();
1346 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1349 pos.y += sb_heading_fontsize.y;
1350 if(panel.current_panel_bg != "0")
1351 pos.y += panel_bg_border;
1353 // Draw the scoreboard
1354 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1357 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1361 vector panel_bg_color_save = panel_bg_color;
1362 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1363 if(panel.current_panel_bg != "0")
1364 team_score_baseoffset.x -= panel_bg_border;
1365 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1367 if(tm.team == NUM_SPECTATOR)
1372 draw_beginBoldFont();
1373 vector rgb = Team_ColorRGB(tm.team);
1374 str = ftos(tm.(teamscores(ts_primary)));
1375 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1377 if(ts_primary != ts_secondary)
1379 str = ftos(tm.(teamscores(ts_secondary)));
1380 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);
1383 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1384 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1385 else if(panel_bg_color_team > 0)
1386 panel_bg_color = rgb * panel_bg_color_team;
1388 panel_bg_color = rgb;
1389 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1391 panel_bg_color = panel_bg_color_save;
1395 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1397 if(tm.team == NUM_SPECTATOR)
1400 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1404 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1405 if(race_speedaward) {
1406 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);
1407 pos.y += 1.25 * hud_fontsize.y;
1409 if(race_speedaward_alltimebest) {
1410 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);
1411 pos.y += 1.25 * hud_fontsize.y;
1413 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1415 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1416 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1418 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1423 for(pl = players.sort_next; pl; pl = pl.sort_next)
1425 if(pl.team != NUM_SPECTATOR)
1427 pos.y += 1.25 * hud_fontsize.y;
1428 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1434 draw_beginBoldFont();
1435 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1437 pos.y += 1.25 * hud_fontsize.y;
1440 // Print info string
1442 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1443 tl = STAT(TIMELIMIT);
1444 fl = STAT(FRAGLIMIT);
1445 ll = STAT(LEADLIMIT);
1446 if(gametype == MAPINFO_TYPE_LMS)
1449 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1454 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1458 str = strcat(str, _(" or"));
1461 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1462 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1463 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1464 TranslateScoresLabel(teamscores_label(ts_primary))));
1468 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1469 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1470 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1471 TranslateScoresLabel(scores_label(ps_primary))));
1476 if(tl > 0 || fl > 0)
1477 str = strcat(str, _(" or"));
1480 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1481 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1482 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1483 TranslateScoresLabel(teamscores_label(ts_primary))));
1487 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1488 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1489 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1490 TranslateScoresLabel(scores_label(ps_primary))));
1495 pos.y += 1.2 * hud_fontsize.y;
1496 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1498 // print information about respawn status
1499 float respawn_time = STAT(RESPAWN_TIME);
1503 if(respawn_time < 0)
1505 // a negative number means we are awaiting respawn, time value is still the same
1506 respawn_time *= -1; // remove mark now that we checked it
1508 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1509 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1511 str = sprintf(_("^1Respawning in ^3%s^1..."),
1512 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1513 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1515 count_seconds(ceil(respawn_time - time))
1519 else if(time < respawn_time)
1521 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1522 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1523 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1525 count_seconds(ceil(respawn_time - time))
1529 else if(time >= respawn_time)
1530 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1532 pos.y += 1.2 * hud_fontsize.y;
1533 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1536 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;