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>
15 float sbt_fg_alpha_self;
17 float sbt_highlight_alpha;
18 float sbt_highlight_alpha_self;
20 // provide basic panel cvars to old clients
21 // TODO remove them after a future release (0.8.2+)
22 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
23 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
24 string autocvar_hud_panel_scoreboard_bg = "border_default";
25 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
26 string autocvar_hud_panel_scoreboard_bg_color_team = "";
27 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
28 string autocvar_hud_panel_scoreboard_bg_border = "";
29 string autocvar_hud_panel_scoreboard_bg_padding = "";
31 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
32 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
33 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
34 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
35 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
36 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
37 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
38 bool autocvar_hud_panel_scoreboard_table_highlight = true;
39 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
40 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
41 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
43 bool autocvar_hud_panel_scoreboard_accuracy = true;
44 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
45 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
47 bool autocvar_hud_panel_scoreboard_dynamichud = false;
50 void drawstringright(vector, string, vector, vector, float, float);
51 void drawstringcenter(vector, string, vector, vector, float, float);
53 // wrapper to put all possible scores titles through gettext
54 string TranslateScoresLabel(string l)
58 case "bckills": return CTX(_("SCO^bckills"));
59 case "bctime": return CTX(_("SCO^bctime"));
60 case "caps": return CTX(_("SCO^caps"));
61 case "captime": return CTX(_("SCO^captime"));
62 case "deaths": return CTX(_("SCO^deaths"));
63 case "destroyed": return CTX(_("SCO^destroyed"));
64 case "dmg": return CTX(_("SCO^damage"));
65 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
66 case "drops": return CTX(_("SCO^drops"));
67 case "faults": return CTX(_("SCO^faults"));
68 case "fckills": return CTX(_("SCO^fckills"));
69 case "goals": return CTX(_("SCO^goals"));
70 case "kckills": return CTX(_("SCO^kckills"));
71 case "kdratio": return CTX(_("SCO^kdratio"));
72 case "kd": return CTX(_("SCO^k/d"));
73 case "kdr": return CTX(_("SCO^kdr"));
74 case "kills": return CTX(_("SCO^kills"));
75 case "laps": return CTX(_("SCO^laps"));
76 case "lives": return CTX(_("SCO^lives"));
77 case "losses": return CTX(_("SCO^losses"));
78 case "name": return CTX(_("SCO^name"));
79 case "sum": return CTX(_("SCO^sum"));
80 case "nick": return CTX(_("SCO^nick"));
81 case "objectives": return CTX(_("SCO^objectives"));
82 case "pickups": return CTX(_("SCO^pickups"));
83 case "ping": return CTX(_("SCO^ping"));
84 case "pl": return CTX(_("SCO^pl"));
85 case "pushes": return CTX(_("SCO^pushes"));
86 case "rank": return CTX(_("SCO^rank"));
87 case "returns": return CTX(_("SCO^returns"));
88 case "revivals": return CTX(_("SCO^revivals"));
89 case "rounds": return CTX(_("SCO^rounds won"));
90 case "score": return CTX(_("SCO^score"));
91 case "suicides": return CTX(_("SCO^suicides"));
92 case "takes": return CTX(_("SCO^takes"));
93 case "ticks": return CTX(_("SCO^ticks"));
98 void Scoreboard_InitScores()
102 ps_primary = ps_secondary = NULL;
103 ts_primary = ts_secondary = -1;
104 FOREACH(Scores, true, {
105 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
106 if(f == SFL_SORT_PRIO_PRIMARY)
108 if(f == SFL_SORT_PRIO_SECONDARY)
111 if(ps_secondary == NULL)
112 ps_secondary = ps_primary;
114 for(i = 0; i < MAX_TEAMSCORE; ++i)
116 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
117 if(f == SFL_SORT_PRIO_PRIMARY)
119 if(f == SFL_SORT_PRIO_SECONDARY)
122 if(ts_secondary == -1)
123 ts_secondary = ts_primary;
125 Cmd_Scoreboard_SetFields(0);
128 float SetTeam(entity pl, float Team);
130 void Scoreboard_UpdatePlayerTeams()
137 for(pl = players.sort_next; pl; pl = pl.sort_next)
140 Team = entcs_GetScoreTeam(pl.sv_entnum);
141 if(SetTeam(pl, Team))
144 Scoreboard_UpdatePlayerPos(pl);
148 pl = players.sort_next;
153 print(strcat("PNUM: ", ftos(num), "\n"));
158 int Scoreboard_CompareScore(int vl, int vr, int f)
160 TC(int, vl); TC(int, vr); TC(int, f);
161 if(f & SFL_ZERO_IS_WORST)
163 if(vl == 0 && vr != 0)
165 if(vl != 0 && vr == 0)
169 return IS_INCREASING(f);
171 return IS_DECREASING(f);
175 float Scoreboard_ComparePlayerScores(entity left, entity right)
178 vl = entcs_GetTeam(left.sv_entnum);
179 vr = entcs_GetTeam(right.sv_entnum);
191 if(vl == NUM_SPECTATOR)
193 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
195 if(!left.gotscores && right.gotscores)
200 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
204 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
208 FOREACH(Scores, true, {
209 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
210 if (r >= 0) return r;
213 if (left.sv_entnum < right.sv_entnum)
219 void Scoreboard_UpdatePlayerPos(entity player)
222 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
224 SORT_SWAP(player, ent);
226 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
228 SORT_SWAP(ent, player);
232 float Scoreboard_CompareTeamScores(entity left, entity right)
236 if(left.team == NUM_SPECTATOR)
238 if(right.team == NUM_SPECTATOR)
241 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
245 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
249 for(i = 0; i < MAX_TEAMSCORE; ++i)
251 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
256 if (left.team < right.team)
262 void Scoreboard_UpdateTeamPos(entity Team)
265 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
267 SORT_SWAP(Team, ent);
269 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
271 SORT_SWAP(ent, Team);
275 void Cmd_Scoreboard_Help()
277 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
278 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
279 LOG_INFO(_("Usage:\n"));
280 LOG_INFO(_("^2scoreboard_columns_set default\n"));
281 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
282 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
283 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
286 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
287 LOG_INFO(_("^3ping^7 Ping time\n"));
288 LOG_INFO(_("^3pl^7 Packet loss\n"));
289 LOG_INFO(_("^3elo^7 Player ELO\n"));
290 LOG_INFO(_("^3kills^7 Number of kills\n"));
291 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
292 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
293 LOG_INFO(_("^3frags^7 kills - suicides\n"));
294 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
295 LOG_INFO(_("^3dmg^7 The total damage done\n"));
296 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
297 LOG_INFO(_("^3sum^7 frags - deaths\n"));
298 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
299 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
300 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
301 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
302 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
303 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
304 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
305 LOG_INFO(_("^3rank^7 Player rank\n"));
306 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
307 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
308 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
309 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
310 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
311 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
312 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
313 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
314 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
315 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
316 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
317 LOG_INFO(_("^3score^7 Total score\n"));
320 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
321 "of game types, then a slash, to make the field show up only in these\n"
322 "or in all but these game types. You can also specify 'all' as a\n"
323 "field to show all fields available for the current game mode.\n\n"));
325 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
326 "include/exclude ALL teams/noteams game modes.\n\n"));
328 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
329 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
330 "right of the vertical bar aligned to the right.\n"));
331 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
332 "other gamemodes except DM.\n"));
335 // NOTE: adding a gametype with ? to not warn for an optional field
336 // make sure it's excluded in a previous exclusive rule, if any
337 // otherwise the previous exclusive rule warns anyway
338 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
339 #define SCOREBOARD_DEFAULT_COLUMNS \
341 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
342 " -teams,lms/deaths +ft,tdm/deaths" \
343 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
344 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
345 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
346 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
347 " +lms/lives +lms/rank" \
348 " +kh/caps +kh/pushes +kh/destroyed" \
349 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
350 " +as/objectives +nb/faults +nb/goals" \
351 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
352 " -lms,rc,cts,inv,nb/score"
354 void Cmd_Scoreboard_SetFields(int argc)
359 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
363 return; // do nothing, we don't know gametype and scores yet
365 // sbt_fields uses strunzone on the titles!
366 if(!sbt_field_title[0])
367 for(i = 0; i < MAX_SBT_FIELDS; ++i)
368 sbt_field_title[i] = strzone("(null)");
370 // TODO: re enable with gametype dependant cvars?
371 if(argc < 3) // no arguments provided
372 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
375 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
379 if(argv(2) == "default")
380 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
381 else if(argv(2) == "all")
384 s = "ping pl name |";
385 FOREACH(Scores, true, {
387 if(it != ps_secondary)
388 if(scores_label(it) != "")
389 s = strcat(s, " ", scores_label(it));
391 if(ps_secondary != ps_primary)
392 s = strcat(s, " ", scores_label(ps_secondary));
393 s = strcat(s, " ", scores_label(ps_primary));
394 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
401 hud_fontsize = HUD_GetFontsize("hud_fontsize");
403 for(i = 1; i < argc - 1; ++i)
409 if(substring(str, 0, 1) == "?")
412 str = substring(str, 1, strlen(str) - 1);
415 slash = strstrofs(str, "/", 0);
418 pattern = substring(str, 0, slash);
419 str = substring(str, slash + 1, strlen(str) - (slash + 1));
421 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
425 strunzone(sbt_field_title[sbt_num_fields]);
426 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
427 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
428 str = strtolower(str);
433 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
434 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
435 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
436 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
437 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
438 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
439 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
440 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
441 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
444 FOREACH(Scores, true, {
445 if (str == strtolower(scores_label(it))) {
447 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
457 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
461 sbt_field[sbt_num_fields] = j;
464 if(j == ps_secondary)
470 if(sbt_num_fields >= MAX_SBT_FIELDS)
474 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
476 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
478 if(ps_primary == ps_secondary)
480 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
482 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
486 strunzone(sbt_field_title[sbt_num_fields]);
487 for(i = sbt_num_fields; i > 0; --i)
489 sbt_field_title[i] = sbt_field_title[i-1];
490 sbt_field_size[i] = sbt_field_size[i-1];
491 sbt_field[i] = sbt_field[i-1];
493 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
494 sbt_field[0] = SP_NAME;
496 LOG_INFO("fixed missing field 'name'\n");
500 strunzone(sbt_field_title[sbt_num_fields]);
501 for(i = sbt_num_fields; i > 1; --i)
503 sbt_field_title[i] = sbt_field_title[i-1];
504 sbt_field_size[i] = sbt_field_size[i-1];
505 sbt_field[i] = sbt_field[i-1];
507 sbt_field_title[1] = strzone("|");
508 sbt_field[1] = SP_SEPARATOR;
509 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
511 LOG_INFO("fixed missing field '|'\n");
514 else if(!have_separator)
516 strunzone(sbt_field_title[sbt_num_fields]);
517 sbt_field_title[sbt_num_fields] = strzone("|");
518 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
519 sbt_field[sbt_num_fields] = SP_SEPARATOR;
521 LOG_INFO("fixed missing field '|'\n");
525 strunzone(sbt_field_title[sbt_num_fields]);
526 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
527 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
528 sbt_field[sbt_num_fields] = ps_secondary;
530 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
536 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
537 sbt_field[sbt_num_fields] = ps_primary;
539 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
543 sbt_field[sbt_num_fields] = SP_END;
547 vector sbt_field_rgb;
548 string sbt_field_icon0;
549 string sbt_field_icon1;
550 string sbt_field_icon2;
551 vector sbt_field_icon0_rgb;
552 vector sbt_field_icon1_rgb;
553 vector sbt_field_icon2_rgb;
554 float sbt_field_icon0_alpha;
555 float sbt_field_icon1_alpha;
556 float sbt_field_icon2_alpha;
557 string Scoreboard_GetField(entity pl, PlayerScoreField field)
559 float tmp, num, denom;
562 sbt_field_rgb = '1 1 1';
563 sbt_field_icon0 = "";
564 sbt_field_icon1 = "";
565 sbt_field_icon2 = "";
566 sbt_field_icon0_rgb = '1 1 1';
567 sbt_field_icon1_rgb = '1 1 1';
568 sbt_field_icon2_rgb = '1 1 1';
569 sbt_field_icon0_alpha = 1;
570 sbt_field_icon1_alpha = 1;
571 sbt_field_icon2_alpha = 1;
576 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
577 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
581 tmp = max(0, min(220, f-80)) / 220;
582 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
588 f = pl.ping_packetloss;
589 tmp = pl.ping_movementloss;
590 if(f == 0 && tmp == 0)
592 str = ftos(ceil(f * 100));
594 str = strcat(str, "~", ftos(ceil(tmp * 100)));
595 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
596 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
600 if(ready_waiting && pl.ready)
602 sbt_field_icon0 = "gfx/scoreboard/player_ready";
606 f = entcs_GetClientColors(pl.sv_entnum);
608 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
609 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
610 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
611 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
612 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
615 return entcs_GetName(pl.sv_entnum);
618 f = pl.(scores(SP_KILLS));
619 f -= pl.(scores(SP_SUICIDES));
623 num = pl.(scores(SP_KILLS));
624 denom = pl.(scores(SP_DEATHS));
627 sbt_field_rgb = '0 1 0';
628 str = sprintf("%d", num);
629 } else if(num <= 0) {
630 sbt_field_rgb = '1 0 0';
631 str = sprintf("%.1f", num/denom);
633 str = sprintf("%.1f", num/denom);
637 f = pl.(scores(SP_KILLS));
638 f -= pl.(scores(SP_DEATHS));
641 sbt_field_rgb = '0 1 0';
643 sbt_field_rgb = '1 1 1';
645 sbt_field_rgb = '1 0 0';
651 float elo = pl.(scores(SP_ELO));
653 case -1: return "...";
654 case -2: return _("N/A");
655 default: return ftos(elo);
660 num = pl.(scores(SP_DMG));
663 str = sprintf("%.1f k", num/denom);
667 num = pl.(scores(SP_DMGTAKEN));
670 str = sprintf("%.1f k", num/denom);
674 tmp = pl.(scores(field));
675 f = scores_flags(field);
676 if(field == ps_primary)
677 sbt_field_rgb = '1 1 0';
678 else if(field == ps_secondary)
679 sbt_field_rgb = '0 1 1';
681 sbt_field_rgb = '1 1 1';
682 return ScoreString(f, tmp);
687 float sbt_fixcolumnwidth_len;
688 float sbt_fixcolumnwidth_iconlen;
689 float sbt_fixcolumnwidth_marginlen;
691 string Scoreboard_FixColumnWidth(int i, string str)
696 PlayerScoreField field = sbt_field[i];
698 sbt_fixcolumnwidth_iconlen = 0;
700 if(sbt_field_icon0 != "")
702 sz = draw_getimagesize(sbt_field_icon0);
704 if(sbt_fixcolumnwidth_iconlen < f)
705 sbt_fixcolumnwidth_iconlen = f;
708 if(sbt_field_icon1 != "")
710 sz = draw_getimagesize(sbt_field_icon1);
712 if(sbt_fixcolumnwidth_iconlen < f)
713 sbt_fixcolumnwidth_iconlen = f;
716 if(sbt_field_icon2 != "")
718 sz = draw_getimagesize(sbt_field_icon2);
720 if(sbt_fixcolumnwidth_iconlen < f)
721 sbt_fixcolumnwidth_iconlen = f;
724 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
726 if(sbt_fixcolumnwidth_iconlen != 0)
727 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
729 sbt_fixcolumnwidth_marginlen = 0;
731 if(field == SP_NAME) // name gets all remaining space
735 namesize = panel_size.x;
736 for(j = 0; j < sbt_num_fields; ++j)
738 if (sbt_field[i] != SP_SEPARATOR)
739 namesize -= sbt_field_size[j] + hud_fontsize.x;
740 sbt_field_size[i] = namesize;
742 if (sbt_fixcolumnwidth_iconlen != 0)
743 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
744 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
745 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
748 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
750 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 if(sbt_field_size[i] < f)
752 sbt_field_size[i] = f;
757 vector Scoreboard_DrawHeader(vector pos, vector rgb)
760 vector column_dim = eY * panel_size.y;
761 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
762 pos.x += hud_fontsize.x * 0.5;
763 for(i = 0; i < sbt_num_fields; ++i)
765 if(sbt_field[i] == SP_SEPARATOR)
767 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
770 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
771 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
772 pos.x += column_dim.x;
774 if(sbt_field[i] == SP_SEPARATOR)
776 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
777 for(i = sbt_num_fields - 1; i > 0; --i)
779 if(sbt_field[i] == SP_SEPARATOR)
782 pos.x -= sbt_field_size[i];
787 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
788 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
791 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
792 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
793 pos.x -= hud_fontsize.x;
798 pos.y += 1.25 * hud_fontsize.y;
802 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
804 TC(bool, is_self); TC(int, pl_number);
806 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
808 vector h_pos = item_pos;
809 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
810 // alternated rows highlighting
812 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
813 else if((sbt_highlight) && (!(pl_number % 2)))
814 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
816 vector pos = item_pos;
817 pos.x += hud_fontsize.x * 0.5;
818 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
819 vector tmp = '0 0 0';
821 PlayerScoreField field;
822 for(i = 0; i < sbt_num_fields; ++i)
824 field = sbt_field[i];
825 if(field == SP_SEPARATOR)
828 if(is_spec && field != SP_NAME && field != SP_PING) {
829 pos.x += sbt_field_size[i] + hud_fontsize.x;
832 str = Scoreboard_GetField(pl, field);
833 str = Scoreboard_FixColumnWidth(i, str);
835 pos.x += sbt_field_size[i] + hud_fontsize.x;
837 if(field == SP_NAME) {
838 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
840 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
842 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
844 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
846 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
848 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
851 tmp.x = sbt_field_size[i] + hud_fontsize.x;
852 if(sbt_field_icon0 != "")
854 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);
856 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
857 if(sbt_field_icon1 != "")
859 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);
861 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
862 if(sbt_field_icon2 != "")
864 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);
866 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
869 if(sbt_field[i] == SP_SEPARATOR)
871 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
872 for(i = sbt_num_fields-1; i > 0; --i)
874 field = sbt_field[i];
875 if(field == SP_SEPARATOR)
878 if(is_spec && field != SP_NAME && field != SP_PING) {
879 pos.x -= sbt_field_size[i] + hud_fontsize.x;
883 str = Scoreboard_GetField(pl, field);
884 str = Scoreboard_FixColumnWidth(i, str);
886 if(field == SP_NAME) {
887 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
889 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
891 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
893 tmp.x = sbt_fixcolumnwidth_len;
895 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
897 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
900 tmp.x = sbt_field_size[i];
901 if(sbt_field_icon0 != "")
903 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);
905 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon0_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
906 if(sbt_field_icon1 != "")
908 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);
910 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, sbt_field_icon1_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
911 if(sbt_field_icon2 != "")
913 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);
915 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, sbt_field_icon2_alpha * sbt_fg_alpha, DRAWFLAG_NORMAL);
916 pos.x -= sbt_field_size[i] + hud_fontsize.x;
921 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
924 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
929 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
930 panel_size.y += panel_bg_padding * 2;
933 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
934 if(panel.current_panel_bg != "0")
935 end_pos.y += panel_bg_border * 2;
939 panel_pos += '1 1 0' * panel_bg_padding;
940 panel_size -= '2 2 0' * panel_bg_padding;
944 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
948 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
950 pos.y += 1.25 * hud_fontsize.y;
953 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
955 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
958 // print header row and highlight columns
959 pos = Scoreboard_DrawHeader(panel_pos, rgb);
961 // fill the table and draw the rows
964 for(pl = players.sort_next; pl; pl = pl.sort_next)
966 if(pl.team != tm.team)
968 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
969 pos.y += 1.25 * hud_fontsize.y;
973 for(pl = players.sort_next; pl; pl = pl.sort_next)
975 if(pl.team == NUM_SPECTATOR)
977 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
978 pos.y += 1.25 * hud_fontsize.y;
982 panel_size.x += panel_bg_padding * 2; // restore initial width
986 float Scoreboard_WouldDraw() {
987 if (QuickMenu_IsOpened())
989 else if (HUD_Radar_Clickable())
991 else if (scoreboard_showscores)
993 else if (intermission == 1)
995 else if (intermission == 2)
997 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
999 else if (scoreboard_showscores_force)
1004 float average_accuracy;
1005 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1007 WepSet weapons_stat = WepSet_GetFromStat();
1008 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1009 int disownedcnt = 0;
1010 FOREACH(Weapons, it != WEP_Null, {
1011 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1013 WepSet set = it.m_wepset;
1014 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1018 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1019 if (weapon_cnt <= 0) return pos;
1022 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1024 int columnns = ceil(weapon_cnt / rows);
1028 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1029 pos.y += 1.25 * hud_fontsize.y;
1030 if(panel.current_panel_bg != "0")
1031 pos.y += panel_bg_border;
1034 panel_size.y = height * rows;
1035 panel_size.y += panel_bg_padding * 2;
1038 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1039 if(panel.current_panel_bg != "0")
1040 end_pos.y += panel_bg_border * 2;
1042 if(panel_bg_padding)
1044 panel_pos += '1 1 0' * panel_bg_padding;
1045 panel_size -= '2 2 0' * panel_bg_padding;
1049 vector tmp = panel_size;
1051 float fontsize = height * 1/3;
1052 float weapon_height = height * 2/3;
1053 float weapon_width = tmp.x / columnns / rows;
1056 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1060 // column highlighting
1061 for (int i = 0; i < columnns; ++i)
1063 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1066 for (int i = 0; i < rows; ++i)
1067 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1070 average_accuracy = 0;
1071 int weapons_with_stats = 0;
1073 pos.x += weapon_width / 2;
1075 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1078 Accuracy_LoadColors();
1080 float oldposx = pos.x;
1084 FOREACH(Weapons, it != WEP_Null, {
1085 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1087 WepSet set = it.m_wepset;
1088 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1092 if (weapon_stats >= 0)
1093 weapon_alpha = sbt_fg_alpha;
1095 weapon_alpha = 0.2 * sbt_fg_alpha;
1098 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1100 if (weapon_stats >= 0) {
1101 weapons_with_stats += 1;
1102 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1105 s = sprintf("%d%%", weapon_stats * 100);
1108 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1110 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1111 rgb = Accuracy_GetColor(weapon_stats);
1113 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1115 tmpos.x += weapon_width * rows;
1116 pos.x += weapon_width * rows;
1117 if (rows == 2 && column == columnns - 1) {
1125 if (weapons_with_stats)
1126 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1128 panel_size.x += panel_bg_padding * 2; // restore initial width
1132 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1134 pos.x += hud_fontsize.x * 0.25;
1135 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1136 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1137 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1139 pos.y += hud_fontsize.y;
1144 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1145 float stat_secrets_found, stat_secrets_total;
1146 float stat_monsters_killed, stat_monsters_total;
1150 // get monster stats
1151 stat_monsters_killed = STAT(MONSTERS_KILLED);
1152 stat_monsters_total = STAT(MONSTERS_TOTAL);
1154 // get secrets stats
1155 stat_secrets_found = STAT(SECRETS_FOUND);
1156 stat_secrets_total = STAT(SECRETS_TOTAL);
1158 // get number of rows
1159 if(stat_secrets_total)
1161 if(stat_monsters_total)
1164 // if no rows, return
1168 // draw table header
1169 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1170 pos.y += 1.25 * hud_fontsize.y;
1171 if(panel.current_panel_bg != "0")
1172 pos.y += panel_bg_border;
1175 panel_size.y = hud_fontsize.y * rows;
1176 panel_size.y += panel_bg_padding * 2;
1179 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1180 if(panel.current_panel_bg != "0")
1181 end_pos.y += panel_bg_border * 2;
1183 if(panel_bg_padding)
1185 panel_pos += '1 1 0' * panel_bg_padding;
1186 panel_size -= '2 2 0' * panel_bg_padding;
1190 vector tmp = panel_size;
1193 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1196 if(stat_monsters_total)
1198 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1199 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1203 if(stat_secrets_total)
1205 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1206 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1209 panel_size.x += panel_bg_padding * 2; // restore initial width
1214 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1217 RANKINGS_RECEIVED_CNT = 0;
1218 for (i=RANKINGS_CNT-1; i>=0; --i)
1220 ++RANKINGS_RECEIVED_CNT;
1222 if (RANKINGS_RECEIVED_CNT == 0)
1225 vector hl_rgb = rgb + '0.5 0.5 0.5';
1227 pos.y += hud_fontsize.y;
1228 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1229 pos.y += 1.25 * hud_fontsize.y;
1230 if(panel.current_panel_bg != "0")
1231 pos.y += panel_bg_border;
1234 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1235 panel_size.y += panel_bg_padding * 2;
1238 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1239 if(panel.current_panel_bg != "0")
1240 end_pos.y += panel_bg_border * 2;
1242 if(panel_bg_padding)
1244 panel_pos += '1 1 0' * panel_bg_padding;
1245 panel_size -= '2 2 0' * panel_bg_padding;
1249 vector tmp = panel_size;
1252 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1255 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1262 n = grecordholder[i];
1263 p = count_ordinal(i+1);
1264 if(grecordholder[i] == entcs_GetName(player_localnum))
1265 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1266 else if(!(i % 2) && sbt_highlight)
1267 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1268 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1269 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);
1270 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1271 pos.y += 1.25 * hud_fontsize.y;
1274 panel_size.x += panel_bg_padding * 2; // restore initial width
1278 void Scoreboard_Draw()
1280 if(!autocvar__hud_configure)
1282 if(!hud_draw_maximized) return;
1284 // frametime checks allow to toggle the scoreboard even when the game is paused
1285 if(scoreboard_active) {
1286 if(hud_configure_menu_open == 1)
1287 scoreboard_fade_alpha = 1;
1288 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1289 if (scoreboard_fadeinspeed && frametime)
1290 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1292 scoreboard_fade_alpha = 1;
1295 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1296 if (scoreboard_fadeoutspeed && frametime)
1297 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1299 scoreboard_fade_alpha = 0;
1302 if (!scoreboard_fade_alpha)
1306 scoreboard_fade_alpha = 0;
1308 if (autocvar_hud_panel_scoreboard_dynamichud)
1311 HUD_Scale_Disable();
1313 if(scoreboard_fade_alpha <= 0)
1315 panel_fade_alpha *= scoreboard_fade_alpha;
1316 HUD_Panel_LoadCvars();
1318 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1319 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1320 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1321 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1322 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1323 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
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)
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)
1396 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1400 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1401 if(race_speedaward) {
1402 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);
1403 pos.y += 1.25 * hud_fontsize.y;
1405 if(race_speedaward_alltimebest) {
1406 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);
1407 pos.y += 1.25 * hud_fontsize.y;
1409 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1411 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1412 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1414 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1419 for(pl = players.sort_next; pl; pl = pl.sort_next)
1421 if(pl.team != NUM_SPECTATOR)
1423 pos.y += 1.25 * hud_fontsize.y;
1424 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1430 draw_beginBoldFont();
1431 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1433 pos.y += 1.25 * hud_fontsize.y;
1436 // Print info string
1438 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1439 tl = STAT(TIMELIMIT);
1440 fl = STAT(FRAGLIMIT);
1441 ll = STAT(LEADLIMIT);
1442 if(gametype == MAPINFO_TYPE_LMS)
1445 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1450 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1454 str = strcat(str, _(" or"));
1457 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1458 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1459 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1460 TranslateScoresLabel(teamscores_label(ts_primary))));
1464 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1465 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1466 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1467 TranslateScoresLabel(scores_label(ps_primary))));
1472 if(tl > 0 || fl > 0)
1473 str = strcat(str, _(" or"));
1476 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1477 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1478 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1479 TranslateScoresLabel(teamscores_label(ts_primary))));
1483 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1484 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1485 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1486 TranslateScoresLabel(scores_label(ps_primary))));
1491 pos.y += 1.2 * hud_fontsize.y;
1492 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1494 // print information about respawn status
1495 float respawn_time = STAT(RESPAWN_TIME);
1499 if(respawn_time < 0)
1501 // a negative number means we are awaiting respawn, time value is still the same
1502 respawn_time *= -1; // remove mark now that we checked it
1504 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1505 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
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(ceil(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(ceil(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;