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.4;
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 + hud_fontsize.y);
928 if(panel.current_panel_bg != "0")
929 end_pos.y += panel_bg_border * 2;
933 panel_pos += '1 1 0' * panel_bg_padding;
934 panel_size -= '2 2 0' * panel_bg_padding;
938 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
942 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
944 pos.y += 1.25 * hud_fontsize.y;
947 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
949 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
952 // print header row and highlight columns
953 pos = Scoreboard_DrawHeader(panel_pos, rgb);
955 // fill the table and draw the rows
958 for(pl = players.sort_next; pl; pl = pl.sort_next)
960 if(pl.team != tm.team)
962 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
963 pos.y += 1.25 * hud_fontsize.y;
967 for(pl = players.sort_next; pl; pl = pl.sort_next)
969 if(pl.team == NUM_SPECTATOR)
971 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
972 pos.y += 1.25 * hud_fontsize.y;
976 panel_size.x += panel_bg_padding * 2; // restore initial width
980 float Scoreboard_WouldDraw() {
981 if (QuickMenu_IsOpened())
983 else if (HUD_Radar_Clickable())
985 else if (scoreboard_showscores)
987 else if (intermission == 1)
989 else if (intermission == 2)
991 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
993 else if (scoreboard_showscores_force)
998 float average_accuracy;
999 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1001 WepSet weapons_stat = WepSet_GetFromStat();
1002 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1003 int disownedcnt = 0;
1004 FOREACH(Weapons, it != WEP_Null, {
1005 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1007 WepSet set = it.m_wepset;
1008 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1012 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1013 if (weapon_cnt <= 0) return pos;
1016 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1018 int columnns = ceil(weapon_cnt / rows);
1022 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1023 pos.y += 1.25 * hud_fontsize.y;
1024 if(panel.current_panel_bg != "0")
1025 pos.y += panel_bg_border;
1028 panel_size.y = height * rows;
1029 panel_size.y += panel_bg_padding * 2;
1030 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1032 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1033 if(panel.current_panel_bg != "0")
1034 end_pos.y += panel_bg_border * 2;
1036 if(panel_bg_padding)
1038 panel_pos += '1 1 0' * panel_bg_padding;
1039 panel_size -= '2 2 0' * panel_bg_padding;
1043 vector tmp = panel_size;
1045 float fontsize = height * 1/3;
1046 float weapon_height = height * 2/3;
1047 float weapon_width = tmp.x / columnns / rows;
1050 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1054 // column highlighting
1055 for (int i = 0; i < columnns; ++i)
1057 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1060 for (int i = 0; i < rows; ++i)
1061 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1064 average_accuracy = 0;
1065 int weapons_with_stats = 0;
1067 pos.x += weapon_width / 2;
1069 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1072 Accuracy_LoadColors();
1074 float oldposx = pos.x;
1078 FOREACH(Weapons, it != WEP_Null, {
1079 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1081 WepSet set = it.m_wepset;
1082 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1086 if (weapon_stats >= 0)
1087 weapon_alpha = sbt_fg_alpha;
1089 weapon_alpha = 0.2 * sbt_fg_alpha;
1092 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1094 if (weapon_stats >= 0) {
1095 weapons_with_stats += 1;
1096 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1099 s = sprintf("%d%%", weapon_stats * 100);
1102 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1104 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1105 rgb = Accuracy_GetColor(weapon_stats);
1107 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1109 tmpos.x += weapon_width * rows;
1110 pos.x += weapon_width * rows;
1111 if (rows == 2 && column == columnns - 1) {
1119 if (weapons_with_stats)
1120 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1122 panel_size.x += panel_bg_padding * 2; // restore initial width
1126 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1128 pos.x += hud_fontsize.x * 0.25;
1129 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1130 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1131 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1133 pos.y += hud_fontsize.y;
1138 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1139 float stat_secrets_found, stat_secrets_total;
1140 float stat_monsters_killed, stat_monsters_total;
1144 // get monster stats
1145 stat_monsters_killed = STAT(MONSTERS_KILLED);
1146 stat_monsters_total = STAT(MONSTERS_TOTAL);
1147 stat_monsters_killed = 14;
1148 stat_monsters_total = 22;
1150 // get secrets stats
1151 stat_secrets_found = STAT(SECRETS_FOUND);
1152 stat_secrets_total = STAT(SECRETS_TOTAL);
1153 stat_secrets_found = 5;
1154 stat_secrets_total = 7;
1156 // get number of rows
1157 if(stat_secrets_total)
1159 if(stat_monsters_total)
1162 // if no rows, return
1166 // draw table header
1167 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1168 pos.y += 1.25 * hud_fontsize.y;
1169 if(panel.current_panel_bg != "0")
1170 pos.y += panel_bg_border;
1173 panel_size.y = hud_fontsize.y * rows;
1174 panel_size.y += panel_bg_padding * 2;
1175 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1177 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1178 if(panel.current_panel_bg != "0")
1179 end_pos.y += panel_bg_border * 2;
1181 if(panel_bg_padding)
1183 panel_pos += '1 1 0' * panel_bg_padding;
1184 panel_size -= '2 2 0' * panel_bg_padding;
1188 vector tmp = panel_size;
1191 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1194 if(stat_monsters_total)
1196 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1197 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1201 if(stat_secrets_total)
1203 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1204 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1207 panel_size.x += panel_bg_padding * 2; // restore initial width
1212 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1215 RANKINGS_RECEIVED_CNT = 0;
1216 for (i=RANKINGS_CNT-1; i>=0; --i)
1218 ++RANKINGS_RECEIVED_CNT;
1220 if (RANKINGS_RECEIVED_CNT == 0)
1223 vector hl_rgb = rgb + '0.5 0.5 0.5';
1225 pos.y += hud_fontsize.y;
1226 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1227 pos.y += 1.25 * hud_fontsize.y;
1228 if(panel.current_panel_bg != "0")
1229 pos.y += panel_bg_border;
1232 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1233 panel_size.y += panel_bg_padding * 2;
1234 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1236 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1237 if(panel.current_panel_bg != "0")
1238 end_pos.y += panel_bg_border * 2;
1240 if(panel_bg_padding)
1242 panel_pos += '1 1 0' * panel_bg_padding;
1243 panel_size -= '2 2 0' * panel_bg_padding;
1247 vector tmp = panel_size;
1250 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1253 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1260 n = grecordholder[i];
1261 p = count_ordinal(i+1);
1262 if(grecordholder[i] == entcs_GetName(player_localnum))
1263 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1264 else if(!(i % 2) && sbt_highlight)
1265 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1266 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1267 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1268 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1269 pos.y += 1.25 * hud_fontsize.y;
1272 panel_size.x += panel_bg_padding * 2; // restore initial width
1276 void Scoreboard_Draw()
1278 if(!autocvar__hud_configure)
1280 // frametime checks allow to toggle the scoreboard even when the game is paused
1281 if(scoreboard_active) {
1282 if(menu_enabled == 1)
1283 scoreboard_fade_alpha = 1;
1284 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1285 if (scoreboard_fadeinspeed && frametime)
1286 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1288 scoreboard_fade_alpha = 1;
1291 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1292 if (scoreboard_fadeoutspeed && frametime)
1293 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1295 scoreboard_fade_alpha = 0;
1298 if (!scoreboard_fade_alpha)
1302 scoreboard_fade_alpha = 0;
1304 if (autocvar_hud_panel_scoreboard_dynamichud)
1307 HUD_Scale_Disable();
1309 float hud_fade_alpha_save = hud_fade_alpha;
1310 if(menu_enabled == 1)
1313 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1314 HUD_Panel_UpdateCvars();
1316 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1317 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1318 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1319 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1320 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1321 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1323 hud_fade_alpha = hud_fade_alpha_save;
1325 // don't overlap with con_notify
1326 if(!autocvar__hud_configure)
1327 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1329 Scoreboard_UpdatePlayerTeams();
1335 // Initializes position
1339 vector sb_heading_fontsize;
1340 sb_heading_fontsize = hud_fontsize * 2;
1341 draw_beginBoldFont();
1342 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1345 pos.y += sb_heading_fontsize.y;
1346 if(panel.current_panel_bg != "0")
1347 pos.y += panel_bg_border;
1349 // Draw the scoreboard
1350 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1353 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1357 vector panel_bg_color_save = panel_bg_color;
1358 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1359 if(panel.current_panel_bg != "0")
1360 team_score_baseoffset.x -= panel_bg_border;
1361 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1363 if(tm.team == NUM_SPECTATOR)
1365 if(!tm.team && teamplay)
1368 draw_beginBoldFont();
1369 vector rgb = Team_ColorRGB(tm.team);
1370 str = ftos(tm.(teamscores(ts_primary)));
1371 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1373 if(ts_primary != ts_secondary)
1375 str = ftos(tm.(teamscores(ts_secondary)));
1376 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1379 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1380 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1381 else if(panel_bg_color_team > 0)
1382 panel_bg_color = rgb * panel_bg_color_team;
1384 panel_bg_color = rgb;
1385 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1387 panel_bg_color = panel_bg_color_save;
1391 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1393 if(tm.team == NUM_SPECTATOR)
1395 if(!tm.team && teamplay)
1398 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1402 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1403 if(race_speedaward) {
1404 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);
1405 pos.y += 1.25 * hud_fontsize.y;
1407 if(race_speedaward_alltimebest) {
1408 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);
1409 pos.y += 1.25 * hud_fontsize.y;
1411 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1413 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1414 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1416 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1421 for(pl = players.sort_next; pl; pl = pl.sort_next)
1423 if(pl.team != NUM_SPECTATOR)
1425 pos.y += 1.25 * hud_fontsize.y;
1426 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1432 draw_beginBoldFont();
1433 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1435 pos.y += 1.25 * hud_fontsize.y;
1438 // Print info string
1440 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1441 tl = STAT(TIMELIMIT);
1442 fl = STAT(FRAGLIMIT);
1443 ll = STAT(LEADLIMIT);
1444 if(gametype == MAPINFO_TYPE_LMS)
1447 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1452 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1456 str = strcat(str, _(" or"));
1459 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1460 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1461 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1462 TranslateScoresLabel(teamscores_label(ts_primary))));
1466 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1467 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1468 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1469 TranslateScoresLabel(scores_label(ps_primary))));
1474 if(tl > 0 || fl > 0)
1475 str = strcat(str, _(" or"));
1478 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1479 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1480 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1481 TranslateScoresLabel(teamscores_label(ts_primary))));
1485 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1486 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1487 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1488 TranslateScoresLabel(scores_label(ps_primary))));
1493 pos.y += 1.2 * hud_fontsize.y;
1494 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1496 // print information about respawn status
1497 float respawn_time = STAT(RESPAWN_TIME);
1501 if(respawn_time < 0)
1503 // a negative number means we are awaiting respawn, time value is still the same
1504 respawn_time *= -1; // remove mark now that we checked it
1505 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1507 str = sprintf(_("^1Respawning in ^3%s^1..."),
1508 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1509 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1511 count_seconds(respawn_time - time)
1515 else if(time < respawn_time)
1517 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1518 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1519 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1521 count_seconds(respawn_time - time)
1525 else if(time >= respawn_time)
1526 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1528 pos.y += 1.2 * hud_fontsize.y;
1529 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1532 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;