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 string autocvar_hud_fontsize;
14 string hud_fontsize_str;
18 float sbt_fg_alpha_self;
20 float sbt_highlight_alpha;
21 float sbt_highlight_alpha_self;
23 // provide basic panel cvars to old clients
24 // TODO remove them after a future release (0.8.2+)
25 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
26 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
27 string autocvar_hud_panel_scoreboard_bg = "border_default";
28 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
29 string autocvar_hud_panel_scoreboard_bg_color_team = "";
30 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
31 string autocvar_hud_panel_scoreboard_bg_border = "";
32 string autocvar_hud_panel_scoreboard_bg_padding = "";
34 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
35 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
36 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
37 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
38 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
39 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
40 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
41 bool autocvar_hud_panel_scoreboard_table_highlight = true;
42 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
43 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
44 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
46 bool autocvar_hud_panel_scoreboard_accuracy = true;
47 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
48 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
50 bool autocvar_hud_panel_scoreboard_dynamichud = false;
53 void drawstringright(vector, string, vector, vector, float, float);
54 void drawstringcenter(vector, string, vector, vector, float, float);
56 // wrapper to put all possible scores titles through gettext
57 string TranslateScoresLabel(string l)
61 case "bckills": return CTX(_("SCO^bckills"));
62 case "bctime": return CTX(_("SCO^bctime"));
63 case "caps": return CTX(_("SCO^caps"));
64 case "captime": return CTX(_("SCO^captime"));
65 case "deaths": return CTX(_("SCO^deaths"));
66 case "destroyed": return CTX(_("SCO^destroyed"));
67 case "dmg": return CTX(_("SCO^damage"));
68 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
69 case "drops": return CTX(_("SCO^drops"));
70 case "faults": return CTX(_("SCO^faults"));
71 case "fckills": return CTX(_("SCO^fckills"));
72 case "goals": return CTX(_("SCO^goals"));
73 case "kckills": return CTX(_("SCO^kckills"));
74 case "kdratio": return CTX(_("SCO^kdratio"));
75 case "kd": return CTX(_("SCO^k/d"));
76 case "kdr": return CTX(_("SCO^kdr"));
77 case "kills": return CTX(_("SCO^kills"));
78 case "laps": return CTX(_("SCO^laps"));
79 case "lives": return CTX(_("SCO^lives"));
80 case "losses": return CTX(_("SCO^losses"));
81 case "name": return CTX(_("SCO^name"));
82 case "sum": return CTX(_("SCO^sum"));
83 case "nick": return CTX(_("SCO^nick"));
84 case "objectives": return CTX(_("SCO^objectives"));
85 case "pickups": return CTX(_("SCO^pickups"));
86 case "ping": return CTX(_("SCO^ping"));
87 case "pl": return CTX(_("SCO^pl"));
88 case "pushes": return CTX(_("SCO^pushes"));
89 case "rank": return CTX(_("SCO^rank"));
90 case "returns": return CTX(_("SCO^returns"));
91 case "revivals": return CTX(_("SCO^revivals"));
92 case "rounds": return CTX(_("SCO^rounds won"));
93 case "score": return CTX(_("SCO^score"));
94 case "suicides": return CTX(_("SCO^suicides"));
95 case "takes": return CTX(_("SCO^takes"));
96 case "ticks": return CTX(_("SCO^ticks"));
101 void Scoreboard_InitScores()
105 ps_primary = ps_secondary = NULL;
106 ts_primary = ts_secondary = -1;
107 FOREACH(Scores, true, {
108 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
109 if(f == SFL_SORT_PRIO_PRIMARY)
111 if(f == SFL_SORT_PRIO_SECONDARY)
114 if(ps_secondary == NULL)
115 ps_secondary = ps_primary;
117 for(i = 0; i < MAX_TEAMSCORE; ++i)
119 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
120 if(f == SFL_SORT_PRIO_PRIMARY)
122 if(f == SFL_SORT_PRIO_SECONDARY)
125 if(ts_secondary == -1)
126 ts_secondary = ts_primary;
128 Cmd_Scoreboard_SetFields(0);
131 float SetTeam(entity pl, float Team);
133 void Scoreboard_UpdatePlayerTeams()
140 for(pl = players.sort_next; pl; pl = pl.sort_next)
143 Team = entcs_GetScoreTeam(pl.sv_entnum);
144 if(SetTeam(pl, Team))
147 Scoreboard_UpdatePlayerPos(pl);
151 pl = players.sort_next;
156 print(strcat("PNUM: ", ftos(num), "\n"));
161 int Scoreboard_CompareScore(int vl, int vr, int f)
163 TC(int, vl); TC(int, vr); TC(int, f);
164 if(f & SFL_ZERO_IS_WORST)
166 if(vl == 0 && vr != 0)
168 if(vl != 0 && vr == 0)
172 return IS_INCREASING(f);
174 return IS_DECREASING(f);
178 float Scoreboard_ComparePlayerScores(entity left, entity right)
181 vl = entcs_GetTeam(left.sv_entnum);
182 vr = entcs_GetTeam(right.sv_entnum);
194 if(vl == NUM_SPECTATOR)
196 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
198 if(!left.gotscores && right.gotscores)
203 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
207 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
211 FOREACH(Scores, true, {
212 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
213 if (r >= 0) return r;
216 if (left.sv_entnum < right.sv_entnum)
222 void Scoreboard_UpdatePlayerPos(entity player)
225 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
227 SORT_SWAP(player, ent);
229 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
231 SORT_SWAP(ent, player);
235 float Scoreboard_CompareTeamScores(entity left, entity right)
239 if(left.team == NUM_SPECTATOR)
241 if(right.team == NUM_SPECTATOR)
244 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
248 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
252 for(i = 0; i < MAX_TEAMSCORE; ++i)
254 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
259 if (left.team < right.team)
265 void Scoreboard_UpdateTeamPos(entity Team)
268 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
270 SORT_SWAP(Team, ent);
272 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
274 SORT_SWAP(ent, Team);
278 void Cmd_Scoreboard_Help()
280 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
281 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
282 LOG_INFO(_("Usage:\n"));
283 LOG_INFO(_("^2scoreboard_columns_set default\n"));
284 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
285 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
286 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
289 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
290 LOG_INFO(_("^3ping^7 Ping time\n"));
291 LOG_INFO(_("^3pl^7 Packet loss\n"));
292 LOG_INFO(_("^3elo^7 Player ELO\n"));
293 LOG_INFO(_("^3kills^7 Number of kills\n"));
294 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
295 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
296 LOG_INFO(_("^3frags^7 kills - suicides\n"));
297 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
298 LOG_INFO(_("^3dmg^7 The total damage done\n"));
299 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
300 LOG_INFO(_("^3sum^7 frags - deaths\n"));
301 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
302 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
303 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
304 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
305 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
306 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
307 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
308 LOG_INFO(_("^3rank^7 Player rank\n"));
309 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
310 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
311 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
312 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
313 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
314 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
315 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
316 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
317 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
318 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
319 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
320 LOG_INFO(_("^3score^7 Total score\n"));
323 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
324 "of game types, then a slash, to make the field show up only in these\n"
325 "or in all but these game types. You can also specify 'all' as a\n"
326 "field to show all fields available for the current game mode.\n\n"));
328 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
329 "include/exclude ALL teams/noteams game modes.\n\n"));
331 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
332 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
333 "right of the vertical bar aligned to the right.\n"));
334 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
335 "other gamemodes except DM.\n"));
338 // NOTE: adding a gametype with ? to not warn for an optional field
339 // make sure it's excluded in a previous exclusive rule, if any
340 // otherwise the previous exclusive rule warns anyway
341 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
342 #define SCOREBOARD_DEFAULT_COLUMNS \
344 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
345 " -teams,lms/deaths +ft,tdm/deaths" \
346 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
347 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
348 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
349 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
350 " +lms/lives +lms/rank" \
351 " +kh/caps +kh/pushes +kh/destroyed" \
352 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
353 " +as/objectives +nb/faults +nb/goals" \
354 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
355 " -lms,rc,cts,inv,nb/score"
357 void Cmd_Scoreboard_SetFields(int argc)
362 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
366 return; // do nothing, we don't know gametype and scores yet
368 // sbt_fields uses strunzone on the titles!
369 if(!sbt_field_title[0])
370 for(i = 0; i < MAX_SBT_FIELDS; ++i)
371 sbt_field_title[i] = strzone("(null)");
373 // TODO: re enable with gametype dependant cvars?
374 if(argc < 3) // no arguments provided
375 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
378 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
382 if(argv(2) == "default")
383 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
384 else if(argv(2) == "all")
387 s = "ping pl name |";
388 FOREACH(Scores, true, {
390 if(it != ps_secondary)
391 if(scores_label(it) != "")
392 s = strcat(s, " ", scores_label(it));
394 if(ps_secondary != ps_primary)
395 s = strcat(s, " ", scores_label(ps_secondary));
396 s = strcat(s, " ", scores_label(ps_primary));
397 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
404 hud_fontsize = HUD_GetFontsize("hud_fontsize");
406 for(i = 1; i < argc - 1; ++i)
412 if(substring(str, 0, 1) == "?")
415 str = substring(str, 1, strlen(str) - 1);
418 slash = strstrofs(str, "/", 0);
421 pattern = substring(str, 0, slash);
422 str = substring(str, slash + 1, strlen(str) - (slash + 1));
424 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
428 strunzone(sbt_field_title[sbt_num_fields]);
429 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
430 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
431 str = strtolower(str);
436 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
437 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
438 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
439 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
440 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
441 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
442 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
443 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
444 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
447 FOREACH(Scores, true, {
448 if (str == strtolower(scores_label(it))) {
450 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
460 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
464 sbt_field[sbt_num_fields] = j;
467 if(j == ps_secondary)
473 if(sbt_num_fields >= MAX_SBT_FIELDS)
477 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
479 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
481 if(ps_primary == ps_secondary)
483 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
485 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
489 strunzone(sbt_field_title[sbt_num_fields]);
490 for(i = sbt_num_fields; i > 0; --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[0] = strzone(TranslateScoresLabel("name"));
497 sbt_field[0] = SP_NAME;
499 LOG_INFO("fixed missing field 'name'\n");
503 strunzone(sbt_field_title[sbt_num_fields]);
504 for(i = sbt_num_fields; i > 1; --i)
506 sbt_field_title[i] = sbt_field_title[i-1];
507 sbt_field_size[i] = sbt_field_size[i-1];
508 sbt_field[i] = sbt_field[i-1];
510 sbt_field_title[1] = strzone("|");
511 sbt_field[1] = SP_SEPARATOR;
512 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
514 LOG_INFO("fixed missing field '|'\n");
517 else if(!have_separator)
519 strunzone(sbt_field_title[sbt_num_fields]);
520 sbt_field_title[sbt_num_fields] = strzone("|");
521 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
522 sbt_field[sbt_num_fields] = SP_SEPARATOR;
524 LOG_INFO("fixed missing field '|'\n");
528 strunzone(sbt_field_title[sbt_num_fields]);
529 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
530 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
531 sbt_field[sbt_num_fields] = ps_secondary;
533 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
537 strunzone(sbt_field_title[sbt_num_fields]);
538 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
539 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
540 sbt_field[sbt_num_fields] = ps_primary;
542 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
546 sbt_field[sbt_num_fields] = SP_END;
550 vector sbt_field_rgb;
551 string sbt_field_icon0;
552 string sbt_field_icon1;
553 string sbt_field_icon2;
554 vector sbt_field_icon0_rgb;
555 vector sbt_field_icon1_rgb;
556 vector sbt_field_icon2_rgb;
557 float sbt_field_icon0_alpha;
558 float sbt_field_icon1_alpha;
559 float sbt_field_icon2_alpha;
560 string Scoreboard_GetField(entity pl, PlayerScoreField field)
562 float tmp, num, denom;
565 sbt_field_rgb = '1 1 1';
566 sbt_field_icon0 = "";
567 sbt_field_icon1 = "";
568 sbt_field_icon2 = "";
569 sbt_field_icon0_rgb = '1 1 1';
570 sbt_field_icon1_rgb = '1 1 1';
571 sbt_field_icon2_rgb = '1 1 1';
572 sbt_field_icon0_alpha = 1;
573 sbt_field_icon1_alpha = 1;
574 sbt_field_icon2_alpha = 1;
579 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
580 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
584 tmp = max(0, min(220, f-80)) / 220;
585 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
591 f = pl.ping_packetloss;
592 tmp = pl.ping_movementloss;
593 if(f == 0 && tmp == 0)
595 str = ftos(ceil(f * 100));
597 str = strcat(str, "~", ftos(ceil(tmp * 100)));
598 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
599 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
603 if(ready_waiting && pl.ready)
605 sbt_field_icon0 = "gfx/scoreboard/player_ready";
609 f = entcs_GetClientColors(pl.sv_entnum);
611 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
612 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
613 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
614 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
615 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
618 return entcs_GetName(pl.sv_entnum);
621 f = pl.(scores(SP_KILLS));
622 f -= pl.(scores(SP_SUICIDES));
626 num = pl.(scores(SP_KILLS));
627 denom = pl.(scores(SP_DEATHS));
630 sbt_field_rgb = '0 1 0';
631 str = sprintf("%d", num);
632 } else if(num <= 0) {
633 sbt_field_rgb = '1 0 0';
634 str = sprintf("%.1f", num/denom);
636 str = sprintf("%.1f", num/denom);
640 f = pl.(scores(SP_KILLS));
641 f -= pl.(scores(SP_DEATHS));
644 sbt_field_rgb = '0 1 0';
646 sbt_field_rgb = '1 1 1';
648 sbt_field_rgb = '1 0 0';
654 float elo = pl.(scores(SP_ELO));
656 case -1: return "...";
657 case -2: return _("N/A");
658 default: return ftos(elo);
662 case SP_DMG: case SP_DMGTAKEN:
663 return sprintf("%.1f k", pl.(scores(field)) / 1000);
666 tmp = pl.(scores(field));
667 f = scores_flags(field);
668 if(field == ps_primary)
669 sbt_field_rgb = '1 1 0';
670 else if(field == ps_secondary)
671 sbt_field_rgb = '0 1 1';
673 sbt_field_rgb = '1 1 1';
674 return ScoreString(f, tmp);
679 float sbt_fixcolumnwidth_len;
680 float sbt_fixcolumnwidth_iconlen;
681 float sbt_fixcolumnwidth_marginlen;
683 string Scoreboard_FixColumnWidth(int i, string str)
689 sbt_fixcolumnwidth_iconlen = 0;
691 if(sbt_field_icon0 != "")
693 sz = draw_getimagesize(sbt_field_icon0);
695 if(sbt_fixcolumnwidth_iconlen < f)
696 sbt_fixcolumnwidth_iconlen = f;
699 if(sbt_field_icon1 != "")
701 sz = draw_getimagesize(sbt_field_icon1);
703 if(sbt_fixcolumnwidth_iconlen < f)
704 sbt_fixcolumnwidth_iconlen = f;
707 if(sbt_field_icon2 != "")
709 sz = draw_getimagesize(sbt_field_icon2);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
717 if(sbt_fixcolumnwidth_iconlen != 0)
718 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
720 sbt_fixcolumnwidth_marginlen = 0;
722 if(sbt_field[i] == SP_NAME) // name gets all remaining space
726 namesize = panel_size.x;
727 for(j = 0; j < sbt_num_fields; ++j)
729 if (sbt_field[i] != SP_SEPARATOR)
730 namesize -= sbt_field_size[j] + hud_fontsize.x;
731 sbt_field_size[i] = namesize;
733 if (sbt_fixcolumnwidth_iconlen != 0)
734 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
735 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
736 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
739 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
741 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
742 if(sbt_field_size[i] < f)
743 sbt_field_size[i] = f;
748 void Scoreboard_initFieldSizes()
750 for(int i = 0; i < sbt_num_fields; ++i)
751 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
754 vector Scoreboard_DrawHeader(vector pos, vector rgb)
757 vector column_dim = eY * panel_size.y;
758 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
759 pos.x += hud_fontsize.x * 0.5;
760 for(i = 0; i < sbt_num_fields; ++i)
762 if(sbt_field[i] == SP_SEPARATOR)
764 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
767 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
768 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
769 pos.x += column_dim.x;
771 if(sbt_field[i] == SP_SEPARATOR)
773 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
774 for(i = sbt_num_fields - 1; i > 0; --i)
776 if(sbt_field[i] == SP_SEPARATOR)
779 pos.x -= sbt_field_size[i];
784 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
785 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
788 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
789 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
790 pos.x -= hud_fontsize.x;
795 pos.y += 1.25 * hud_fontsize.y;
799 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
801 TC(bool, is_self); TC(int, pl_number);
803 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
805 vector h_pos = item_pos;
806 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
807 // alternated rows highlighting
809 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
810 else if((sbt_highlight) && (!(pl_number % 2)))
811 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
813 vector pos = item_pos;
814 pos.x += hud_fontsize.x * 0.5;
815 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
816 vector tmp = '0 0 0';
818 PlayerScoreField field;
819 for(i = 0; i < sbt_num_fields; ++i)
821 field = sbt_field[i];
822 if(field == SP_SEPARATOR)
825 if(is_spec && field != SP_NAME && field != SP_PING) {
826 pos.x += sbt_field_size[i] + hud_fontsize.x;
829 str = Scoreboard_GetField(pl, field);
830 str = Scoreboard_FixColumnWidth(i, str);
832 pos.x += sbt_field_size[i] + hud_fontsize.x;
834 if(field == SP_NAME) {
835 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
837 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
839 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
841 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
843 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
845 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
848 tmp.x = sbt_field_size[i] + hud_fontsize.x;
849 if(sbt_field_icon0 != "")
851 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);
853 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
854 if(sbt_field_icon1 != "")
856 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);
858 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
859 if(sbt_field_icon2 != "")
861 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);
863 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
866 if(sbt_field[i] == SP_SEPARATOR)
868 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
869 for(i = sbt_num_fields-1; i > 0; --i)
871 field = sbt_field[i];
872 if(field == SP_SEPARATOR)
875 if(is_spec && field != SP_NAME && field != SP_PING) {
876 pos.x -= sbt_field_size[i] + hud_fontsize.x;
880 str = Scoreboard_GetField(pl, field);
881 str = Scoreboard_FixColumnWidth(i, str);
883 if(field == SP_NAME) {
884 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
886 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
888 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
890 tmp.x = sbt_fixcolumnwidth_len;
892 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
894 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
897 tmp.x = sbt_field_size[i];
898 if(sbt_field_icon0 != "")
900 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);
902 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
903 if(sbt_field_icon1 != "")
905 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);
907 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
908 if(sbt_field_icon2 != "")
910 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);
912 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
913 pos.x -= sbt_field_size[i] + hud_fontsize.x;
918 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
921 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
926 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
927 panel_size.y += panel_bg_padding * 2;
930 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
931 if(panel.current_panel_bg != "0")
932 end_pos.y += panel_bg_border * 2;
936 panel_pos += '1 1 0' * panel_bg_padding;
937 panel_size -= '2 2 0' * panel_bg_padding;
941 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
945 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
947 pos.y += 1.25 * hud_fontsize.y;
950 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
952 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
955 // print header row and highlight columns
956 pos = Scoreboard_DrawHeader(panel_pos, rgb);
958 // fill the table and draw the rows
961 for(pl = players.sort_next; pl; pl = pl.sort_next)
963 if(pl.team != tm.team)
965 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
966 pos.y += 1.25 * hud_fontsize.y;
970 for(pl = players.sort_next; pl; pl = pl.sort_next)
972 if(pl.team == NUM_SPECTATOR)
974 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
975 pos.y += 1.25 * hud_fontsize.y;
979 panel_size.x += panel_bg_padding * 2; // restore initial width
983 float Scoreboard_WouldDraw() {
984 if (QuickMenu_IsOpened())
986 else if (HUD_Radar_Clickable())
988 else if (scoreboard_showscores)
990 else if (intermission == 1)
992 else if (intermission == 2)
994 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
996 else if (scoreboard_showscores_force)
1001 float average_accuracy;
1002 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1004 WepSet weapons_stat = WepSet_GetFromStat();
1005 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1006 int disownedcnt = 0;
1008 FOREACH(Weapons, it != WEP_Null, {
1009 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1011 WepSet set = it.m_wepset;
1012 if (weapon_stats < 0)
1014 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1016 else if (!(weapons_stat & set || weapons_inmap & set))
1021 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1022 if (weapon_cnt <= 0) return pos;
1025 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1027 int columnns = ceil(weapon_cnt / rows);
1029 float weapon_height = 29;
1030 float height = hud_fontsize.y + weapon_height;
1032 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1033 pos.y += 1.25 * hud_fontsize.y;
1034 if(panel.current_panel_bg != "0")
1035 pos.y += panel_bg_border;
1038 panel_size.y = height * rows;
1039 panel_size.y += panel_bg_padding * 2;
1042 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1043 if(panel.current_panel_bg != "0")
1044 end_pos.y += panel_bg_border * 2;
1046 if(panel_bg_padding)
1048 panel_pos += '1 1 0' * panel_bg_padding;
1049 panel_size -= '2 2 0' * panel_bg_padding;
1053 vector tmp = panel_size;
1055 float weapon_width = tmp.x / columnns / rows;
1058 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1062 // column highlighting
1063 for (int i = 0; i < columnns; ++i)
1065 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1068 for (int i = 0; i < rows; ++i)
1069 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1072 average_accuracy = 0;
1073 int weapons_with_stats = 0;
1075 pos.x += weapon_width / 2;
1077 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1080 Accuracy_LoadColors();
1082 float oldposx = pos.x;
1086 FOREACH(Weapons, it != WEP_Null, {
1087 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1089 WepSet set = it.m_wepset;
1090 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1094 if (weapon_stats >= 0)
1095 weapon_alpha = sbt_fg_alpha;
1097 weapon_alpha = 0.2 * sbt_fg_alpha;
1100 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1102 if (weapon_stats >= 0) {
1103 weapons_with_stats += 1;
1104 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1107 s = sprintf("%d%%", weapon_stats * 100);
1110 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1112 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1113 rgb = Accuracy_GetColor(weapon_stats);
1115 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1117 tmpos.x += weapon_width * rows;
1118 pos.x += weapon_width * rows;
1119 if (rows == 2 && column == columnns - 1) {
1127 if (weapons_with_stats)
1128 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1130 panel_size.x += panel_bg_padding * 2; // restore initial width
1134 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1136 pos.x += hud_fontsize.x * 0.25;
1137 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1138 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1139 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1141 pos.y += hud_fontsize.y;
1146 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1147 float stat_secrets_found, stat_secrets_total;
1148 float stat_monsters_killed, stat_monsters_total;
1152 // get monster stats
1153 stat_monsters_killed = STAT(MONSTERS_KILLED);
1154 stat_monsters_total = STAT(MONSTERS_TOTAL);
1156 // get secrets stats
1157 stat_secrets_found = STAT(SECRETS_FOUND);
1158 stat_secrets_total = STAT(SECRETS_TOTAL);
1160 // get number of rows
1161 if(stat_secrets_total)
1163 if(stat_monsters_total)
1166 // if no rows, return
1170 // draw table header
1171 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1172 pos.y += 1.25 * hud_fontsize.y;
1173 if(panel.current_panel_bg != "0")
1174 pos.y += panel_bg_border;
1177 panel_size.y = hud_fontsize.y * rows;
1178 panel_size.y += panel_bg_padding * 2;
1181 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1182 if(panel.current_panel_bg != "0")
1183 end_pos.y += panel_bg_border * 2;
1185 if(panel_bg_padding)
1187 panel_pos += '1 1 0' * panel_bg_padding;
1188 panel_size -= '2 2 0' * panel_bg_padding;
1192 vector tmp = panel_size;
1195 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1198 if(stat_monsters_total)
1200 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1201 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1205 if(stat_secrets_total)
1207 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1208 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1211 panel_size.x += panel_bg_padding * 2; // restore initial width
1216 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1219 RANKINGS_RECEIVED_CNT = 0;
1220 for (i=RANKINGS_CNT-1; i>=0; --i)
1222 ++RANKINGS_RECEIVED_CNT;
1224 if (RANKINGS_RECEIVED_CNT == 0)
1227 vector hl_rgb = rgb + '0.5 0.5 0.5';
1229 pos.y += hud_fontsize.y;
1230 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1231 pos.y += 1.25 * hud_fontsize.y;
1232 if(panel.current_panel_bg != "0")
1233 pos.y += panel_bg_border;
1236 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1237 panel_size.y += panel_bg_padding * 2;
1240 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1241 if(panel.current_panel_bg != "0")
1242 end_pos.y += panel_bg_border * 2;
1244 if(panel_bg_padding)
1246 panel_pos += '1 1 0' * panel_bg_padding;
1247 panel_size -= '2 2 0' * panel_bg_padding;
1251 vector tmp = panel_size;
1254 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1257 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1264 n = grecordholder[i];
1265 p = count_ordinal(i+1);
1266 if(grecordholder[i] == entcs_GetName(player_localnum))
1267 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1268 else if(!(i % 2) && sbt_highlight)
1269 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1270 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1271 drawstring(pos + '3 0 0' * hud_fontsize.y, TIME_ENCODED_TOSTRING(t), '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1272 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1273 pos.y += 1.25 * hud_fontsize.y;
1276 panel_size.x += panel_bg_padding * 2; // restore initial width
1280 void Scoreboard_Draw()
1282 if(!autocvar__hud_configure)
1284 if(!hud_draw_maximized) return;
1286 // frametime checks allow to toggle the scoreboard even when the game is paused
1287 if(scoreboard_active) {
1288 if(hud_configure_menu_open == 1)
1289 scoreboard_fade_alpha = 1;
1290 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1291 if (scoreboard_fadeinspeed && frametime)
1292 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1294 scoreboard_fade_alpha = 1;
1295 if(hud_fontsize_str != autocvar_hud_fontsize)
1297 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1298 Scoreboard_initFieldSizes();
1299 if(hud_fontsize_str)
1300 strunzone(hud_fontsize_str);
1301 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1305 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1306 if (scoreboard_fadeoutspeed && frametime)
1307 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1309 scoreboard_fade_alpha = 0;
1312 if (!scoreboard_fade_alpha)
1316 scoreboard_fade_alpha = 0;
1318 if (autocvar_hud_panel_scoreboard_dynamichud)
1321 HUD_Scale_Disable();
1323 if(scoreboard_fade_alpha <= 0)
1325 panel_fade_alpha *= scoreboard_fade_alpha;
1326 HUD_Panel_LoadCvars();
1328 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1329 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1330 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1331 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1332 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1333 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1335 // don't overlap with con_notify
1336 if(!autocvar__hud_configure)
1337 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1339 Scoreboard_UpdatePlayerTeams();
1345 // Initializes position
1349 vector sb_heading_fontsize;
1350 sb_heading_fontsize = hud_fontsize * 2;
1351 draw_beginBoldFont();
1352 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1355 pos.y += sb_heading_fontsize.y;
1356 if(panel.current_panel_bg != "0")
1357 pos.y += panel_bg_border;
1359 // Draw the scoreboard
1360 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1363 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1367 vector panel_bg_color_save = panel_bg_color;
1368 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1369 if(panel.current_panel_bg != "0")
1370 team_score_baseoffset.x -= panel_bg_border;
1371 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1373 if(tm.team == NUM_SPECTATOR)
1378 draw_beginBoldFont();
1379 vector rgb = Team_ColorRGB(tm.team);
1380 str = ftos(tm.(teamscores(ts_primary)));
1381 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1383 if(ts_primary != ts_secondary)
1385 str = ftos(tm.(teamscores(ts_secondary)));
1386 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);
1389 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1390 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1391 else if(panel_bg_color_team > 0)
1392 panel_bg_color = rgb * panel_bg_color_team;
1394 panel_bg_color = rgb;
1395 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1397 panel_bg_color = panel_bg_color_save;
1401 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1403 if(tm.team == NUM_SPECTATOR)
1406 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1410 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1411 if(race_speedaward) {
1412 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);
1413 pos.y += 1.25 * hud_fontsize.y;
1415 if(race_speedaward_alltimebest) {
1416 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);
1417 pos.y += 1.25 * hud_fontsize.y;
1419 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1421 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1422 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1424 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1429 for(pl = players.sort_next; pl; pl = pl.sort_next)
1431 if(pl.team != NUM_SPECTATOR)
1433 pos.y += 1.25 * hud_fontsize.y;
1434 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1440 draw_beginBoldFont();
1441 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1443 pos.y += 1.25 * hud_fontsize.y;
1446 // Print info string
1448 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1449 tl = STAT(TIMELIMIT);
1450 fl = STAT(FRAGLIMIT);
1451 ll = STAT(LEADLIMIT);
1452 if(gametype == MAPINFO_TYPE_LMS)
1455 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1460 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1464 str = strcat(str, _(" or"));
1467 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1468 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1469 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1470 TranslateScoresLabel(teamscores_label(ts_primary))));
1474 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1475 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1476 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1477 TranslateScoresLabel(scores_label(ps_primary))));
1482 if(tl > 0 || fl > 0)
1483 str = strcat(str, _(" or"));
1486 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1487 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1488 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1489 TranslateScoresLabel(teamscores_label(ts_primary))));
1493 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1494 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1495 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1496 TranslateScoresLabel(scores_label(ps_primary))));
1501 pos.y += 1.2 * hud_fontsize.y;
1502 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1504 // print information about respawn status
1505 float respawn_time = STAT(RESPAWN_TIME);
1509 if(respawn_time < 0)
1511 // a negative number means we are awaiting respawn, time value is still the same
1512 respawn_time *= -1; // remove mark now that we checked it
1514 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1515 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1517 str = sprintf(_("^1Respawning in ^3%s^1..."),
1518 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1519 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1521 count_seconds(ceil(respawn_time - time))
1525 else if(time < respawn_time)
1527 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1528 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1529 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1531 count_seconds(ceil(respawn_time - time))
1535 else if(time >= respawn_time)
1536 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1538 pos.y += 1.2 * hud_fontsize.y;
1539 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1542 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;