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);
1148 // get secrets stats
1149 stat_secrets_found = STAT(SECRETS_FOUND);
1150 stat_secrets_total = STAT(SECRETS_TOTAL);
1152 // get number of rows
1153 if(stat_secrets_total)
1155 if(stat_monsters_total)
1158 // if no rows, return
1162 // draw table header
1163 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1164 pos.y += 1.25 * hud_fontsize.y;
1165 if(panel.current_panel_bg != "0")
1166 pos.y += panel_bg_border;
1169 panel_size.y = hud_fontsize.y * rows;
1170 panel_size.y += panel_bg_padding * 2;
1171 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1173 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1174 if(panel.current_panel_bg != "0")
1175 end_pos.y += panel_bg_border * 2;
1177 if(panel_bg_padding)
1179 panel_pos += '1 1 0' * panel_bg_padding;
1180 panel_size -= '2 2 0' * panel_bg_padding;
1184 vector tmp = panel_size;
1187 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1190 if(stat_monsters_total)
1192 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1193 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1197 if(stat_secrets_total)
1199 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1200 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1203 panel_size.x += panel_bg_padding * 2; // restore initial width
1208 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1211 RANKINGS_RECEIVED_CNT = 0;
1212 for (i=RANKINGS_CNT-1; i>=0; --i)
1214 ++RANKINGS_RECEIVED_CNT;
1216 if (RANKINGS_RECEIVED_CNT == 0)
1219 vector hl_rgb = rgb + '0.5 0.5 0.5';
1221 pos.y += hud_fontsize.y;
1222 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1223 pos.y += 1.25 * hud_fontsize.y;
1224 if(panel.current_panel_bg != "0")
1225 pos.y += panel_bg_border;
1228 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1229 panel_size.y += panel_bg_padding * 2;
1230 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1232 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1233 if(panel.current_panel_bg != "0")
1234 end_pos.y += panel_bg_border * 2;
1236 if(panel_bg_padding)
1238 panel_pos += '1 1 0' * panel_bg_padding;
1239 panel_size -= '2 2 0' * panel_bg_padding;
1243 vector tmp = panel_size;
1246 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1249 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1256 n = grecordholder[i];
1257 p = count_ordinal(i+1);
1258 if(grecordholder[i] == entcs_GetName(player_localnum))
1259 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1260 else if(!(i % 2) && sbt_highlight)
1261 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1262 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1263 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);
1264 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1265 pos.y += 1.25 * hud_fontsize.y;
1268 panel_size.x += panel_bg_padding * 2; // restore initial width
1272 void Scoreboard_Draw()
1274 if(!autocvar__hud_configure)
1276 // frametime checks allow to toggle the scoreboard even when the game is paused
1277 if(scoreboard_active) {
1278 if(menu_enabled == 1)
1279 scoreboard_fade_alpha = 1;
1280 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1281 if (scoreboard_fadeinspeed && frametime)
1282 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1284 scoreboard_fade_alpha = 1;
1287 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1288 if (scoreboard_fadeoutspeed && frametime)
1289 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1291 scoreboard_fade_alpha = 0;
1294 if (!scoreboard_fade_alpha)
1298 scoreboard_fade_alpha = 0;
1300 if (autocvar_hud_panel_scoreboard_dynamichud)
1303 HUD_Scale_Disable();
1305 float hud_fade_alpha_save = hud_fade_alpha;
1306 if(menu_enabled == 1)
1309 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1310 HUD_Panel_UpdateCvars();
1312 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1313 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1314 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1315 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1316 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1317 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1319 hud_fade_alpha = hud_fade_alpha_save;
1321 // don't overlap with con_notify
1322 if(!autocvar__hud_configure)
1323 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1325 Scoreboard_UpdatePlayerTeams();
1331 // Initializes position
1335 vector sb_heading_fontsize;
1336 sb_heading_fontsize = hud_fontsize * 2;
1337 draw_beginBoldFont();
1338 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1341 pos.y += sb_heading_fontsize.y;
1342 if(panel.current_panel_bg != "0")
1343 pos.y += panel_bg_border;
1345 // Draw the scoreboard
1346 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1349 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1353 vector panel_bg_color_save = panel_bg_color;
1354 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1355 if(panel.current_panel_bg != "0")
1356 team_score_baseoffset.x -= panel_bg_border;
1357 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1359 if(tm.team == NUM_SPECTATOR)
1361 if(!tm.team && teamplay)
1364 draw_beginBoldFont();
1365 vector rgb = Team_ColorRGB(tm.team);
1366 str = ftos(tm.(teamscores(ts_primary)));
1367 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1369 if(ts_primary != ts_secondary)
1371 str = ftos(tm.(teamscores(ts_secondary)));
1372 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);
1375 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1376 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1377 else if(panel_bg_color_team > 0)
1378 panel_bg_color = rgb * panel_bg_color_team;
1380 panel_bg_color = rgb;
1381 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1383 panel_bg_color = panel_bg_color_save;
1387 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1389 if(tm.team == NUM_SPECTATOR)
1391 if(!tm.team && teamplay)
1394 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1398 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1399 if(race_speedaward) {
1400 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);
1401 pos.y += 1.25 * hud_fontsize.y;
1403 if(race_speedaward_alltimebest) {
1404 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);
1405 pos.y += 1.25 * hud_fontsize.y;
1407 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1409 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1410 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1412 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1417 for(pl = players.sort_next; pl; pl = pl.sort_next)
1419 if(pl.team != NUM_SPECTATOR)
1421 pos.y += 1.25 * hud_fontsize.y;
1422 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1428 draw_beginBoldFont();
1429 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1431 pos.y += 1.25 * hud_fontsize.y;
1434 // Print info string
1436 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1437 tl = STAT(TIMELIMIT);
1438 fl = STAT(FRAGLIMIT);
1439 ll = STAT(LEADLIMIT);
1440 if(gametype == MAPINFO_TYPE_LMS)
1443 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1448 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1452 str = strcat(str, _(" or"));
1455 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1456 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1457 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1458 TranslateScoresLabel(teamscores_label(ts_primary))));
1462 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1463 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1464 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1465 TranslateScoresLabel(scores_label(ps_primary))));
1470 if(tl > 0 || fl > 0)
1471 str = strcat(str, _(" or"));
1474 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1475 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1476 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1477 TranslateScoresLabel(teamscores_label(ts_primary))));
1481 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1482 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1483 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1484 TranslateScoresLabel(scores_label(ps_primary))));
1489 pos.y += 1.2 * hud_fontsize.y;
1490 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1492 // print information about respawn status
1493 float respawn_time = STAT(RESPAWN_TIME);
1497 if(respawn_time < 0)
1499 // a negative number means we are awaiting respawn, time value is still the same
1500 respawn_time *= -1; // remove mark now that we checked it
1501 respawn_time = max(time, respawn_time); // don't show a negative value while the server is respawning the player (lag)
1503 str = sprintf(_("^1Respawning in ^3%s^1..."),
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)
1513 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1514 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1515 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1517 count_seconds(respawn_time - time)
1521 else if(time >= respawn_time)
1522 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1524 pos.y += 1.2 * hud_fontsize.y;
1525 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1528 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;