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 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
19 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
20 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
21 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0.7;
22 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
23 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
24 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
25 bool autocvar_hud_panel_scoreboard_table_highlight = true;
26 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
27 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.5;
28 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
30 bool autocvar_hud_panel_scoreboard_accuracy = true;
31 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
32 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
34 bool autocvar_hud_panel_scoreboard_dynamichud = false;
37 void drawstringright(vector, string, vector, vector, float, float);
38 void drawstringcenter(vector, string, vector, vector, float, float);
40 // wrapper to put all possible scores titles through gettext
41 string TranslateScoresLabel(string l)
45 case "bckills": return CTX(_("SCO^bckills"));
46 case "bctime": return CTX(_("SCO^bctime"));
47 case "caps": return CTX(_("SCO^caps"));
48 case "captime": return CTX(_("SCO^captime"));
49 case "deaths": return CTX(_("SCO^deaths"));
50 case "destroyed": return CTX(_("SCO^destroyed"));
51 case "dmg": return CTX(_("SCO^dmg"));
52 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
53 case "drops": return CTX(_("SCO^drops"));
54 case "faults": return CTX(_("SCO^faults"));
55 case "fckills": return CTX(_("SCO^fckills"));
56 case "goals": return CTX(_("SCO^goals"));
57 case "kckills": return CTX(_("SCO^kckills"));
58 case "kdratio": return CTX(_("SCO^kdratio"));
59 case "k/d": return CTX(_("SCO^k/d"));
60 case "kd": return CTX(_("SCO^kd"));
61 case "kdr": return CTX(_("SCO^kdr"));
62 case "kills": return CTX(_("SCO^kills"));
63 case "laps": return CTX(_("SCO^laps"));
64 case "lives": return CTX(_("SCO^lives"));
65 case "losses": return CTX(_("SCO^losses"));
66 case "name": return CTX(_("SCO^name"));
67 case "sum": return CTX(_("SCO^sum"));
68 case "nick": return CTX(_("SCO^nick"));
69 case "objectives": return CTX(_("SCO^objectives"));
70 case "pickups": return CTX(_("SCO^pickups"));
71 case "ping": return CTX(_("SCO^ping"));
72 case "pl": return CTX(_("SCO^pl"));
73 case "pushes": return CTX(_("SCO^pushes"));
74 case "rank": return CTX(_("SCO^rank"));
75 case "returns": return CTX(_("SCO^returns"));
76 case "revivals": return CTX(_("SCO^revivals"));
77 case "score": return CTX(_("SCO^score"));
78 case "suicides": return CTX(_("SCO^suicides"));
79 case "takes": return CTX(_("SCO^takes"));
80 case "ticks": return CTX(_("SCO^ticks"));
85 void Scoreboard_InitScores()
89 ps_primary = ps_secondary = NULL;
90 ts_primary = ts_secondary = -1;
91 FOREACH(Scores, true, {
92 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
93 if(f == SFL_SORT_PRIO_PRIMARY)
95 if(f == SFL_SORT_PRIO_SECONDARY)
98 if(ps_secondary == NULL)
99 ps_secondary = ps_primary;
101 for(i = 0; i < MAX_TEAMSCORE; ++i)
103 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
104 if(f == SFL_SORT_PRIO_PRIMARY)
106 if(f == SFL_SORT_PRIO_SECONDARY)
109 if(ts_secondary == -1)
110 ts_secondary = ts_primary;
112 Cmd_Scoreboard_SetFields(0);
115 float SetTeam(entity pl, float Team);
117 void Scoreboard_UpdatePlayerTeams()
124 for(pl = players.sort_next; pl; pl = pl.sort_next)
127 Team = entcs_GetScoreTeam(pl.sv_entnum);
128 if(SetTeam(pl, Team))
131 Scoreboard_UpdatePlayerPos(pl);
135 pl = players.sort_next;
140 print(strcat("PNUM: ", ftos(num), "\n"));
145 int Scoreboard_CompareScore(int vl, int vr, int f)
147 TC(int, vl); TC(int, vr); TC(int, f);
148 if(f & SFL_ZERO_IS_WORST)
150 if(vl == 0 && vr != 0)
152 if(vl != 0 && vr == 0)
156 return IS_INCREASING(f);
158 return IS_DECREASING(f);
162 float Scoreboard_ComparePlayerScores(entity left, entity right)
165 vl = entcs_GetTeam(left.sv_entnum);
166 vr = entcs_GetTeam(right.sv_entnum);
178 if(vl == NUM_SPECTATOR)
180 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
182 if(!left.gotscores && right.gotscores)
187 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
191 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
195 FOREACH(Scores, true, {
196 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
197 if (r >= 0) return r;
200 if (left.sv_entnum < right.sv_entnum)
206 void Scoreboard_UpdatePlayerPos(entity player)
209 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
211 SORT_SWAP(player, ent);
213 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
215 SORT_SWAP(ent, player);
219 float Scoreboard_CompareTeamScores(entity left, entity right)
223 if(left.team == NUM_SPECTATOR)
225 if(right.team == NUM_SPECTATOR)
228 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
232 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
236 for(i = 0; i < MAX_TEAMSCORE; ++i)
238 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
243 if (left.team < right.team)
249 void Scoreboard_UpdateTeamPos(entity Team)
252 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
254 SORT_SWAP(Team, ent);
256 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
258 SORT_SWAP(ent, Team);
262 void Cmd_Scoreboard_Help()
264 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
265 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
266 LOG_INFO(_("Usage:\n"));
267 LOG_INFO(_("^2scoreboard_columns_set default\n"));
268 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
269 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
270 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
273 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
274 LOG_INFO(_("^3ping^7 Ping time\n"));
275 LOG_INFO(_("^3pl^7 Packet loss\n"));
276 LOG_INFO(_("^3elo^7 Player ELO\n"));
277 LOG_INFO(_("^3kills^7 Number of kills\n"));
278 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
279 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
280 LOG_INFO(_("^3frags^7 kills - suicides\n"));
281 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
282 LOG_INFO(_("^3dmg^7 The total damage done\n"));
283 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
284 LOG_INFO(_("^3sum^7 frags - deaths\n"));
285 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
286 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
287 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
288 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
289 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
290 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
291 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
292 LOG_INFO(_("^3rank^7 Player rank\n"));
293 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
294 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
295 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
296 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
297 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
298 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
299 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
300 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
301 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
302 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
303 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
304 LOG_INFO(_("^3score^7 Total score\n"));
307 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
308 "of game types, then a slash, to make the field show up only in these\n"
309 "or in all but these game types. You can also specify 'all' as a\n"
310 "field to show all fields available for the current game mode.\n\n"));
312 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
313 "include/exclude ALL teams/noteams game modes.\n\n"));
315 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
316 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
317 "right of the vertical bar aligned to the right.\n"));
318 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
319 "other gamemodes except DM.\n"));
322 // NOTE: adding a gametype with ? to not warn for an optional field
323 // make sure it's excluded in a previous exclusive rule, if any
324 // otherwise the previous exclusive rule warns anyway
325 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
326 #define SCOREBOARD_DEFAULT_COLUMNS \
328 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
329 " -teams,lms/deaths +ft,tdm/deaths" \
330 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
331 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
332 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
333 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
334 " +lms/lives +lms/rank" \
335 " +kh/caps +kh/pushes +kh/destroyed" \
336 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
337 " +as/objectives +nb/faults +nb/goals" \
338 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
339 " -lms,rc,cts,inv,nb/score"
341 void Cmd_Scoreboard_SetFields(int argc)
346 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
351 // set up a temporary scoreboard layout
352 // no layout can be properly set up until score_info data haven't been received
353 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
354 ps_primary = SP_SCORE;
355 scores_label(ps_primary) = strzone("score");
356 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
359 // TODO: re enable with gametype dependant cvars?
360 if(argc < 3) // no arguments provided
361 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
364 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
368 if(argv(2) == "default")
369 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
370 else if(argv(2) == "all")
373 s = "ping pl name |";
374 FOREACH(Scores, true, {
376 if(it != ps_secondary)
377 if(scores_label(it) != "")
378 s = strcat(s, " ", scores_label(it));
380 if(ps_secondary != ps_primary)
381 s = strcat(s, " ", scores_label(ps_secondary));
382 s = strcat(s, " ", scores_label(ps_primary));
383 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
390 hud_fontsize = HUD_GetFontsize("hud_fontsize");
392 for(i = 1; i < argc - 1; ++i)
398 if(substring(str, 0, 1) == "?")
401 str = substring(str, 1, strlen(str) - 1);
404 slash = strstrofs(str, "/", 0);
407 pattern = substring(str, 0, slash);
408 str = substring(str, slash + 1, strlen(str) - (slash + 1));
410 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
414 strunzone(sbt_field_title[sbt_num_fields]);
415 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
416 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
417 str = strtolower(str);
422 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
423 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
424 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
425 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
426 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
427 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
428 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
429 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
430 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
433 FOREACH(Scores, true, {
434 if (str == strtolower(scores_label(it))) {
436 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
446 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
450 sbt_field[sbt_num_fields] = j;
453 if(j == ps_secondary)
459 if(sbt_num_fields >= MAX_SBT_FIELDS)
463 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
465 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
467 if(ps_primary == ps_secondary)
469 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
471 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
475 strunzone(sbt_field_title[sbt_num_fields]);
476 for(i = sbt_num_fields; i > 0; --i)
478 sbt_field_title[i] = sbt_field_title[i-1];
479 sbt_field_size[i] = sbt_field_size[i-1];
480 sbt_field[i] = sbt_field[i-1];
482 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
483 sbt_field[0] = SP_NAME;
485 LOG_INFO("fixed missing field 'name'\n");
489 strunzone(sbt_field_title[sbt_num_fields]);
490 for(i = sbt_num_fields; i > 1; --i)
492 sbt_field_title[i] = sbt_field_title[i-1];
493 sbt_field_size[i] = sbt_field_size[i-1];
494 sbt_field[i] = sbt_field[i-1];
496 sbt_field_title[1] = strzone("|");
497 sbt_field[1] = SP_SEPARATOR;
498 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
500 LOG_INFO("fixed missing field '|'\n");
503 else if(!have_separator)
505 strunzone(sbt_field_title[sbt_num_fields]);
506 sbt_field_title[sbt_num_fields] = strzone("|");
507 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
508 sbt_field[sbt_num_fields] = SP_SEPARATOR;
510 LOG_INFO("fixed missing field '|'\n");
514 strunzone(sbt_field_title[sbt_num_fields]);
515 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
516 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
517 sbt_field[sbt_num_fields] = ps_secondary;
519 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
523 strunzone(sbt_field_title[sbt_num_fields]);
524 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
525 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
526 sbt_field[sbt_num_fields] = ps_primary;
528 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
532 sbt_field[sbt_num_fields] = SP_END;
536 vector sbt_field_rgb;
537 string sbt_field_icon0;
538 string sbt_field_icon1;
539 string sbt_field_icon2;
540 vector sbt_field_icon0_rgb;
541 vector sbt_field_icon1_rgb;
542 vector sbt_field_icon2_rgb;
543 float sbt_field_icon0_alpha;
544 float sbt_field_icon1_alpha;
545 float sbt_field_icon2_alpha;
546 string Scoreboard_GetField(entity pl, PlayerScoreField field)
548 float tmp, num, denom;
551 sbt_field_rgb = '1 1 1';
552 sbt_field_icon0 = "";
553 sbt_field_icon1 = "";
554 sbt_field_icon2 = "";
555 sbt_field_icon0_rgb = '1 1 1';
556 sbt_field_icon1_rgb = '1 1 1';
557 sbt_field_icon2_rgb = '1 1 1';
558 sbt_field_icon0_alpha = 1;
559 sbt_field_icon1_alpha = 1;
560 sbt_field_icon2_alpha = 1;
565 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
566 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
570 tmp = max(0, min(220, f-80)) / 220;
571 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
577 f = pl.ping_packetloss;
578 tmp = pl.ping_movementloss;
579 if(f == 0 && tmp == 0)
581 str = ftos(ceil(f * 100));
583 str = strcat(str, "~", ftos(ceil(tmp * 100)));
584 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
585 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
589 if(ready_waiting && pl.ready)
591 sbt_field_icon0 = "gfx/scoreboard/player_ready";
595 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
597 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
598 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
599 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
600 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
601 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
604 return entcs_GetName(pl.sv_entnum);
607 f = pl.(scores(SP_KILLS));
608 f -= pl.(scores(SP_SUICIDES));
612 num = pl.(scores(SP_KILLS));
613 denom = pl.(scores(SP_DEATHS));
616 sbt_field_rgb = '0 1 0';
617 str = sprintf("%d", num);
618 } else if(num <= 0) {
619 sbt_field_rgb = '1 0 0';
620 str = sprintf("%.1f", num/denom);
622 str = sprintf("%.1f", num/denom);
626 f = pl.(scores(SP_KILLS));
627 f -= pl.(scores(SP_DEATHS));
630 sbt_field_rgb = '0 1 0';
632 sbt_field_rgb = '1 1 1';
634 sbt_field_rgb = '1 0 0';
640 float elo = pl.(scores(SP_ELO));
642 case -1: return "...";
643 case -2: return _("N/A");
644 default: return ftos(elo);
649 num = pl.(scores(SP_DMG));
652 str = sprintf("%.1f k", num/denom);
656 num = pl.(scores(SP_DMGTAKEN));
659 str = sprintf("%.1f k", num/denom);
663 tmp = pl.(scores(field));
664 f = scores_flags(field);
665 if(field == ps_primary)
666 sbt_field_rgb = '1 1 0';
667 else if(field == ps_secondary)
668 sbt_field_rgb = '0 1 1';
670 sbt_field_rgb = '1 1 1';
671 return ScoreString(f, tmp);
676 float sbt_fixcolumnwidth_len;
677 float sbt_fixcolumnwidth_iconlen;
678 float sbt_fixcolumnwidth_marginlen;
680 string Scoreboard_FixColumnWidth(int i, string str)
685 PlayerScoreField field = sbt_field[i];
687 sbt_fixcolumnwidth_iconlen = 0;
689 if(sbt_field_icon0 != "")
691 sz = draw_getimagesize(sbt_field_icon0);
693 if(sbt_fixcolumnwidth_iconlen < f)
694 sbt_fixcolumnwidth_iconlen = f;
697 if(sbt_field_icon1 != "")
699 sz = draw_getimagesize(sbt_field_icon1);
701 if(sbt_fixcolumnwidth_iconlen < f)
702 sbt_fixcolumnwidth_iconlen = f;
705 if(sbt_field_icon2 != "")
707 sz = draw_getimagesize(sbt_field_icon2);
709 if(sbt_fixcolumnwidth_iconlen < f)
710 sbt_fixcolumnwidth_iconlen = f;
713 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
715 if(sbt_fixcolumnwidth_iconlen != 0)
716 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
718 sbt_fixcolumnwidth_marginlen = 0;
720 if(field == SP_NAME) // name gets all remaining space
724 namesize = panel_size.x;
725 for(j = 0; j < sbt_num_fields; ++j)
727 if (sbt_field[i] != SP_SEPARATOR)
728 namesize -= sbt_field_size[j] + hud_fontsize.x;
729 sbt_field_size[i] = namesize;
731 if (sbt_fixcolumnwidth_iconlen != 0)
732 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
733 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
734 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
737 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
739 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
740 if(sbt_field_size[i] < f)
741 sbt_field_size[i] = f;
746 vector Scoreboard_DrawHeader(vector pos, vector rgb)
749 vector column_dim = eY * panel_size.y;
750 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
751 pos.x += hud_fontsize.x * 0.5;
752 for(i = 0; i < sbt_num_fields; ++i)
754 if(sbt_field[i] == SP_SEPARATOR)
756 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
759 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
760 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
761 pos.x += column_dim.x;
763 if(sbt_field[i] == SP_SEPARATOR)
765 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
766 for(i = sbt_num_fields - 1; i > 0; --i)
768 if(sbt_field[i] == SP_SEPARATOR)
771 pos.x -= sbt_field_size[i];
776 if (i == sbt_num_fields-1)
777 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
779 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
780 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
783 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
784 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
785 pos.x -= hud_fontsize.x;
790 pos.y += 1.25 * hud_fontsize.y;
794 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
796 TC(bool, is_self); TC(int, pl_number);
798 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
799 if(is_spec && !is_self)
802 vector h_pos = item_pos;
803 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
804 // alternated rows highlighting
806 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
807 else if((sbt_highlight) && (!(pl_number % 2)))
808 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
810 vector pos = item_pos;
811 pos.x += hud_fontsize.x * 0.5;
812 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
813 vector tmp = '0 0 0';
815 PlayerScoreField field;
816 for(i = 0; i < sbt_num_fields; ++i)
818 field = sbt_field[i];
819 if(field == SP_SEPARATOR)
822 if(is_spec && field != SP_NAME && field != SP_PING) {
823 pos.x += sbt_field_size[i] + hud_fontsize.x;
826 str = Scoreboard_GetField(pl, field);
827 str = Scoreboard_FixColumnWidth(i, str);
829 pos.x += sbt_field_size[i] + hud_fontsize.x;
831 if(field == SP_NAME) {
832 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
834 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
836 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
838 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
840 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
842 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
845 tmp.x = sbt_field_size[i] + hud_fontsize.x;
846 if(sbt_field_icon0 != "")
848 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);
850 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);
851 if(sbt_field_icon1 != "")
853 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);
855 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);
856 if(sbt_field_icon2 != "")
858 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);
860 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);
863 if(sbt_field[i] == SP_SEPARATOR)
865 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
866 for(i = sbt_num_fields-1; i > 0; --i)
868 field = sbt_field[i];
869 if(field == SP_SEPARATOR)
872 if(is_spec && field != SP_NAME && field != SP_PING) {
873 pos.x -= sbt_field_size[i] + hud_fontsize.x;
877 str = Scoreboard_GetField(pl, field);
878 str = Scoreboard_FixColumnWidth(i, str);
880 if(field == SP_NAME) {
881 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
883 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
885 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_fixcolumnwidth_len;
889 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
891 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
894 tmp.x = sbt_field_size[i];
895 if(sbt_field_icon0 != "")
897 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);
899 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);
900 if(sbt_field_icon1 != "")
902 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);
904 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);
905 if(sbt_field_icon2 != "")
907 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);
909 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);
910 pos.x -= sbt_field_size[i] + hud_fontsize.x;
915 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
918 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
923 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
924 panel_size.y += panel_bg_padding * 2;
925 HUD_Panel_DrawBg(scoreboard_fade_alpha);
927 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
931 panel_pos += '1 1 0' * panel_bg_padding;
932 panel_size -= '2 2 0' * panel_bg_padding;
936 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
940 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
942 pos.y += 1.25 * hud_fontsize.y;
945 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
947 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
950 // print header row and highlight columns
951 pos = Scoreboard_DrawHeader(panel_pos, rgb);
953 // fill the table and draw the rows
956 for(pl = players.sort_next; pl; pl = pl.sort_next)
958 if(pl.team != tm.team)
960 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
961 pos.y += 1.25 * hud_fontsize.y;
965 for(pl = players.sort_next; pl; pl = pl.sort_next)
967 if(pl.team == NUM_SPECTATOR)
969 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
970 pos.y += 1.25 * hud_fontsize.y;
974 panel_size.x += panel_bg_padding * 2; // restore initial width
978 float Scoreboard_WouldDraw() {
979 if (QuickMenu_IsOpened())
981 else if (HUD_Radar_Clickable())
983 else if (scoreboard_showscores)
985 else if (intermission == 1)
987 else if (intermission == 2)
989 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
991 else if (scoreboard_showscores_force)
996 float average_accuracy;
997 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
999 WepSet weapons_stat = WepSet_GetFromStat();
1000 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1001 int disownedcnt = 0;
1002 FOREACH(Weapons, it != WEP_Null, {
1003 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1005 WepSet set = it.m_wepset;
1006 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1010 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1011 if (weapon_cnt <= 0) return pos;
1014 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1016 int columnns = ceil(weapon_cnt / rows);
1020 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1021 pos.y += 1.25 * hud_fontsize.y;
1022 pos.y += panel_bg_border;
1025 panel_size.y = height * rows;
1026 panel_size.y += panel_bg_padding * 2;
1027 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1029 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1031 if(panel_bg_padding)
1033 panel_pos += '1 1 0' * panel_bg_padding;
1034 panel_size -= '2 2 0' * panel_bg_padding;
1038 vector tmp = panel_size;
1040 float fontsize = height * 1/3;
1041 float weapon_height = height * 2/3;
1042 float weapon_width = tmp.x / columnns / rows;
1045 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1049 // column highlighting
1050 for (int i = 0; i < columnns; ++i)
1052 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1055 for (int i = 0; i < rows; ++i)
1056 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1059 average_accuracy = 0;
1060 int weapons_with_stats = 0;
1062 pos.x += weapon_width / 2;
1064 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1067 Accuracy_LoadColors();
1069 float oldposx = pos.x;
1073 FOREACH(Weapons, it != WEP_Null, {
1074 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1076 WepSet set = it.m_wepset;
1077 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1081 if (weapon_stats >= 0)
1082 weapon_alpha = sbt_fg_alpha;
1084 weapon_alpha = 0.2 * sbt_fg_alpha;
1087 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1089 if (weapon_stats >= 0) {
1090 weapons_with_stats += 1;
1091 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1094 s = sprintf("%d%%", weapon_stats * 100);
1097 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1099 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1100 rgb = Accuracy_GetColor(weapon_stats);
1102 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1104 tmpos.x += weapon_width * rows;
1105 pos.x += weapon_width * rows;
1106 if (rows == 2 && column == columnns - 1) {
1114 if (weapons_with_stats)
1115 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1117 panel_size.x += panel_bg_padding * 2; // restore initial width
1121 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1123 pos.x += hud_fontsize.x * 0.25;
1124 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1125 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1126 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1128 pos.y += hud_fontsize.y;
1133 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1134 float stat_secrets_found, stat_secrets_total;
1135 float stat_monsters_killed, stat_monsters_total;
1139 // get monster stats
1140 stat_monsters_killed = STAT(MONSTERS_KILLED);
1141 stat_monsters_total = STAT(MONSTERS_TOTAL);
1142 stat_monsters_killed = 14;
1143 stat_monsters_total = 22;
1145 // get secrets stats
1146 stat_secrets_found = STAT(SECRETS_FOUND);
1147 stat_secrets_total = STAT(SECRETS_TOTAL);
1148 stat_secrets_found = 5;
1149 stat_secrets_total = 7;
1151 // get number of rows
1152 if(stat_secrets_total)
1154 if(stat_monsters_total)
1157 // if no rows, return
1161 // draw table header
1162 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1163 pos.y += 1.25 * hud_fontsize.y;
1164 pos.y += panel_bg_border;
1167 panel_size.y = hud_fontsize.y * rows;
1168 panel_size.y += panel_bg_padding * 2;
1169 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1171 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1173 if(panel_bg_padding)
1175 panel_pos += '1 1 0' * panel_bg_padding;
1176 panel_size -= '2 2 0' * panel_bg_padding;
1180 vector tmp = panel_size;
1183 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1186 if(stat_monsters_total)
1188 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1189 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1193 if(stat_secrets_total)
1195 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1196 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1199 panel_size.x += panel_bg_padding * 2; // restore initial width
1204 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1207 RANKINGS_RECEIVED_CNT = 0;
1208 for (i=RANKINGS_CNT-1; i>=0; --i)
1210 ++RANKINGS_RECEIVED_CNT;
1212 if (RANKINGS_RECEIVED_CNT == 0)
1215 vector hl_rgb = rgb + '0.5 0.5 0.5';
1217 pos.y += hud_fontsize.y;
1218 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1219 pos.y += 1.25 * hud_fontsize.y;
1220 pos.y += panel_bg_border;
1223 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1224 panel_size.y += panel_bg_padding * 2;
1225 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1227 vector end_pos = panel_pos + eY * (panel_size.y + panel_bg_border * 2 + hud_fontsize.y);
1229 if(panel_bg_padding)
1231 panel_pos += '1 1 0' * panel_bg_padding;
1232 panel_size -= '2 2 0' * panel_bg_padding;
1236 vector tmp = panel_size;
1239 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1242 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1249 n = grecordholder[i];
1250 p = count_ordinal(i+1);
1251 if(grecordholder[i] == entcs_GetName(player_localnum))
1252 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1253 else if(!(i % 2) && sbt_highlight)
1254 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1255 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1256 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);
1257 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1258 pos.y += 1.25 * hud_fontsize.y;
1261 panel_size.x += panel_bg_padding * 2; // restore initial width
1265 void Scoreboard_Draw()
1267 if(!autocvar__hud_configure)
1269 // frametime checks allow to toggle the scoreboard even when the game is paused
1270 if(scoreboard_active) {
1271 if(menu_enabled == 1)
1272 scoreboard_fade_alpha = 1;
1273 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1274 if (scoreboard_fadeinspeed && frametime)
1275 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1277 scoreboard_fade_alpha = 1;
1280 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1281 if (scoreboard_fadeoutspeed && frametime)
1282 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1284 scoreboard_fade_alpha = 0;
1287 if (!scoreboard_fade_alpha)
1291 scoreboard_fade_alpha = 0;
1293 if (autocvar_hud_panel_scoreboard_dynamichud)
1296 HUD_Scale_Disable();
1298 float hud_fade_alpha_save = hud_fade_alpha;
1299 if(menu_enabled == 1)
1302 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1303 HUD_Panel_UpdateCvars();
1305 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1306 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1307 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1308 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1309 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1310 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1312 hud_fade_alpha = hud_fade_alpha_save;
1314 // don't overlap with con_notify
1315 if(!autocvar__hud_configure)
1316 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1318 Scoreboard_UpdatePlayerTeams();
1324 // Initializes position
1328 vector sb_heading_fontsize;
1329 sb_heading_fontsize = hud_fontsize * 2;
1330 draw_beginBoldFont();
1331 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1334 pos.y += sb_heading_fontsize.y;
1335 pos.y += panel_bg_border;
1337 // Draw the scoreboard
1338 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1341 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1345 vector panel_bg_color_save = panel_bg_color;
1346 vector team_score_baseoffset = eY * hud_fontsize.y - eX * (panel_bg_border + hud_fontsize.x * 0.5);
1347 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1349 if(tm.team == NUM_SPECTATOR)
1351 if(!tm.team && teamplay)
1354 draw_beginBoldFont();
1355 vector rgb = Team_ColorRGB(tm.team);
1356 str = ftos(tm.(teamscores(ts_primary)));
1357 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1359 if(ts_primary != ts_secondary)
1361 str = ftos(tm.(teamscores(ts_secondary)));
1362 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);
1365 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1366 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1367 else if(panel_bg_color_team > 0)
1368 panel_bg_color = rgb * panel_bg_color_team;
1370 panel_bg_color = rgb;
1371 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1373 panel_bg_color = panel_bg_color_save;
1377 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1379 if(tm.team == NUM_SPECTATOR)
1381 if(!tm.team && teamplay)
1384 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1388 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1389 if(race_speedaward) {
1390 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);
1391 pos.y += 1.25 * hud_fontsize.y;
1393 if(race_speedaward_alltimebest) {
1394 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);
1395 pos.y += 1.25 * hud_fontsize.y;
1397 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1399 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1400 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1402 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1407 for(pl = players.sort_next; pl; pl = pl.sort_next)
1409 if(pl.team != NUM_SPECTATOR)
1411 pos.y += 1.25 * hud_fontsize.y;
1412 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1418 draw_beginBoldFont();
1419 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1421 pos.y += 1.25 * hud_fontsize.y;
1424 // Print info string
1426 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1427 tl = STAT(TIMELIMIT);
1428 fl = STAT(FRAGLIMIT);
1429 ll = STAT(LEADLIMIT);
1430 if(gametype == MAPINFO_TYPE_LMS)
1433 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1438 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1442 str = strcat(str, _(" or"));
1445 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1446 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1447 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1448 TranslateScoresLabel(teamscores_label(ts_primary))));
1452 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1453 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1454 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1455 TranslateScoresLabel(scores_label(ps_primary))));
1460 if(tl > 0 || fl > 0)
1461 str = strcat(str, _(" or"));
1464 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1465 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1466 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1467 TranslateScoresLabel(teamscores_label(ts_primary))));
1471 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1472 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1473 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1474 TranslateScoresLabel(scores_label(ps_primary))));
1479 pos.y += 1.2 * hud_fontsize.y;
1480 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1482 // print information about respawn status
1483 float respawn_time = STAT(RESPAWN_TIME);
1487 if(respawn_time < 0)
1489 // a negative number means we are awaiting respawn, time value is still the same
1490 respawn_time *= -1; // remove mark now that we checked it
1491 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1493 str = sprintf(_("^1Respawning in ^3%s^1..."),
1494 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1495 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1497 count_seconds(respawn_time - time)
1501 else if(time < respawn_time)
1503 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1504 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1505 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1507 count_seconds(respawn_time - time)
1511 else if(time >= respawn_time)
1512 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1514 pos.y += 1.2 * hud_fontsize.y;
1515 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1518 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;