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 // provide basic panel cvars to old clients
19 // TODO remove them after a future release (0.8.2+)
20 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
21 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
22 string autocvar_hud_panel_scoreboard_bg = "border_default";
23 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
24 string autocvar_hud_panel_scoreboard_bg_color_team = "";
25 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
26 string autocvar_hud_panel_scoreboard_bg_border = "";
27 string autocvar_hud_panel_scoreboard_bg_padding = "";
29 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
30 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
31 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
32 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
33 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
34 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
35 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
36 bool autocvar_hud_panel_scoreboard_table_highlight = true;
37 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
38 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
39 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
41 bool autocvar_hud_panel_scoreboard_accuracy = true;
42 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
43 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
45 bool autocvar_hud_panel_scoreboard_dynamichud = false;
48 void drawstringright(vector, string, vector, vector, float, float);
49 void drawstringcenter(vector, string, vector, vector, float, float);
51 // wrapper to put all possible scores titles through gettext
52 string TranslateScoresLabel(string l)
56 case "bckills": return CTX(_("SCO^bckills"));
57 case "bctime": return CTX(_("SCO^bctime"));
58 case "caps": return CTX(_("SCO^caps"));
59 case "captime": return CTX(_("SCO^captime"));
60 case "deaths": return CTX(_("SCO^deaths"));
61 case "destroyed": return CTX(_("SCO^destroyed"));
62 case "dmg": return CTX(_("SCO^dmg"));
63 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
64 case "drops": return CTX(_("SCO^drops"));
65 case "faults": return CTX(_("SCO^faults"));
66 case "fckills": return CTX(_("SCO^fckills"));
67 case "goals": return CTX(_("SCO^goals"));
68 case "kckills": return CTX(_("SCO^kckills"));
69 case "kdratio": return CTX(_("SCO^kdratio"));
70 case "k/d": return CTX(_("SCO^k/d"));
71 case "kd": return CTX(_("SCO^kd"));
72 case "kdr": return CTX(_("SCO^kdr"));
73 case "kills": return CTX(_("SCO^kills"));
74 case "laps": return CTX(_("SCO^laps"));
75 case "lives": return CTX(_("SCO^lives"));
76 case "losses": return CTX(_("SCO^losses"));
77 case "name": return CTX(_("SCO^name"));
78 case "sum": return CTX(_("SCO^sum"));
79 case "nick": return CTX(_("SCO^nick"));
80 case "objectives": return CTX(_("SCO^objectives"));
81 case "pickups": return CTX(_("SCO^pickups"));
82 case "ping": return CTX(_("SCO^ping"));
83 case "pl": return CTX(_("SCO^pl"));
84 case "pushes": return CTX(_("SCO^pushes"));
85 case "rank": return CTX(_("SCO^rank"));
86 case "returns": return CTX(_("SCO^returns"));
87 case "revivals": return CTX(_("SCO^revivals"));
88 case "rounds": return CTX(_("SCO^rounds won"));
89 case "score": return CTX(_("SCO^score"));
90 case "suicides": return CTX(_("SCO^suicides"));
91 case "takes": return CTX(_("SCO^takes"));
92 case "ticks": return CTX(_("SCO^ticks"));
97 void Scoreboard_InitScores()
101 ps_primary = ps_secondary = NULL;
102 ts_primary = ts_secondary = -1;
103 FOREACH(Scores, true, {
104 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
105 if(f == SFL_SORT_PRIO_PRIMARY)
107 if(f == SFL_SORT_PRIO_SECONDARY)
110 if(ps_secondary == NULL)
111 ps_secondary = ps_primary;
113 for(i = 0; i < MAX_TEAMSCORE; ++i)
115 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
116 if(f == SFL_SORT_PRIO_PRIMARY)
118 if(f == SFL_SORT_PRIO_SECONDARY)
121 if(ts_secondary == -1)
122 ts_secondary = ts_primary;
124 Cmd_Scoreboard_SetFields(0);
127 float SetTeam(entity pl, float Team);
129 void Scoreboard_UpdatePlayerTeams()
136 for(pl = players.sort_next; pl; pl = pl.sort_next)
139 Team = entcs_GetScoreTeam(pl.sv_entnum);
140 if(SetTeam(pl, Team))
143 Scoreboard_UpdatePlayerPos(pl);
147 pl = players.sort_next;
152 print(strcat("PNUM: ", ftos(num), "\n"));
157 int Scoreboard_CompareScore(int vl, int vr, int f)
159 TC(int, vl); TC(int, vr); TC(int, f);
160 if(f & SFL_ZERO_IS_WORST)
162 if(vl == 0 && vr != 0)
164 if(vl != 0 && vr == 0)
168 return IS_INCREASING(f);
170 return IS_DECREASING(f);
174 float Scoreboard_ComparePlayerScores(entity left, entity right)
177 vl = entcs_GetTeam(left.sv_entnum);
178 vr = entcs_GetTeam(right.sv_entnum);
190 if(vl == NUM_SPECTATOR)
192 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
194 if(!left.gotscores && right.gotscores)
199 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
203 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
207 FOREACH(Scores, true, {
208 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
209 if (r >= 0) return r;
212 if (left.sv_entnum < right.sv_entnum)
218 void Scoreboard_UpdatePlayerPos(entity player)
221 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
223 SORT_SWAP(player, ent);
225 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
227 SORT_SWAP(ent, player);
231 float Scoreboard_CompareTeamScores(entity left, entity right)
235 if(left.team == NUM_SPECTATOR)
237 if(right.team == NUM_SPECTATOR)
240 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
244 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
248 for(i = 0; i < MAX_TEAMSCORE; ++i)
250 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
255 if (left.team < right.team)
261 void Scoreboard_UpdateTeamPos(entity Team)
264 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
266 SORT_SWAP(Team, ent);
268 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
270 SORT_SWAP(ent, Team);
274 void Cmd_Scoreboard_Help()
276 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
277 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
278 LOG_INFO(_("Usage:\n"));
279 LOG_INFO(_("^2scoreboard_columns_set default\n"));
280 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
281 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
282 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
285 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
286 LOG_INFO(_("^3ping^7 Ping time\n"));
287 LOG_INFO(_("^3pl^7 Packet loss\n"));
288 LOG_INFO(_("^3elo^7 Player ELO\n"));
289 LOG_INFO(_("^3kills^7 Number of kills\n"));
290 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
291 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
292 LOG_INFO(_("^3frags^7 kills - suicides\n"));
293 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
294 LOG_INFO(_("^3dmg^7 The total damage done\n"));
295 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
296 LOG_INFO(_("^3sum^7 frags - deaths\n"));
297 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
298 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
299 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
300 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
301 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
302 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
303 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
304 LOG_INFO(_("^3rank^7 Player rank\n"));
305 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
306 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
307 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
308 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
309 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
310 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
311 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
312 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
313 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
314 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
315 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
316 LOG_INFO(_("^3score^7 Total score\n"));
319 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
320 "of game types, then a slash, to make the field show up only in these\n"
321 "or in all but these game types. You can also specify 'all' as a\n"
322 "field to show all fields available for the current game mode.\n\n"));
324 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
325 "include/exclude ALL teams/noteams game modes.\n\n"));
327 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
328 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
329 "right of the vertical bar aligned to the right.\n"));
330 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
331 "other gamemodes except DM.\n"));
334 // NOTE: adding a gametype with ? to not warn for an optional field
335 // make sure it's excluded in a previous exclusive rule, if any
336 // otherwise the previous exclusive rule warns anyway
337 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
338 #define SCOREBOARD_DEFAULT_COLUMNS \
340 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
341 " -teams,lms/deaths +ft,tdm/deaths" \
342 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
343 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
344 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
345 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
346 " +lms/lives +lms/rank" \
347 " +kh/caps +kh/pushes +kh/destroyed" \
348 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
349 " +as/objectives +nb/faults +nb/goals" \
350 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
351 " -lms,rc,cts,inv,nb/score"
353 void Cmd_Scoreboard_SetFields(int argc)
358 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
363 // set up a temporary scoreboard layout
364 // no layout can be properly set up until score_info data haven't been received
365 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
366 ps_primary = SP_SCORE;
367 ps_secondary = SP_SCORE;
368 scores_label(ps_primary) = strzone("score");
369 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
372 // TODO: re enable with gametype dependant cvars?
373 if(argc < 3) // no arguments provided
374 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
377 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
381 if(argv(2) == "default")
382 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
383 else if(argv(2) == "all")
386 s = "ping pl name |";
387 FOREACH(Scores, true, {
389 if(it != ps_secondary)
390 if(scores_label(it) != "")
391 s = strcat(s, " ", scores_label(it));
393 if(ps_secondary != ps_primary)
394 s = strcat(s, " ", scores_label(ps_secondary));
395 s = strcat(s, " ", scores_label(ps_primary));
396 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
403 hud_fontsize = HUD_GetFontsize("hud_fontsize");
405 for(i = 1; i < argc - 1; ++i)
411 if(substring(str, 0, 1) == "?")
414 str = substring(str, 1, strlen(str) - 1);
417 slash = strstrofs(str, "/", 0);
420 pattern = substring(str, 0, slash);
421 str = substring(str, slash + 1, strlen(str) - (slash + 1));
423 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
427 strunzone(sbt_field_title[sbt_num_fields]);
428 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
429 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
430 str = strtolower(str);
435 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
436 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
437 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
438 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
439 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
440 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
441 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
442 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
443 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
446 FOREACH(Scores, true, {
447 if (str == strtolower(scores_label(it))) {
449 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
459 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
463 sbt_field[sbt_num_fields] = j;
466 if(j == ps_secondary)
472 if(sbt_num_fields >= MAX_SBT_FIELDS)
476 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
478 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
480 if(ps_primary == ps_secondary)
482 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
484 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
488 strunzone(sbt_field_title[sbt_num_fields]);
489 for(i = sbt_num_fields; i > 0; --i)
491 sbt_field_title[i] = sbt_field_title[i-1];
492 sbt_field_size[i] = sbt_field_size[i-1];
493 sbt_field[i] = sbt_field[i-1];
495 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
496 sbt_field[0] = SP_NAME;
498 LOG_INFO("fixed missing field 'name'\n");
502 strunzone(sbt_field_title[sbt_num_fields]);
503 for(i = sbt_num_fields; i > 1; --i)
505 sbt_field_title[i] = sbt_field_title[i-1];
506 sbt_field_size[i] = sbt_field_size[i-1];
507 sbt_field[i] = sbt_field[i-1];
509 sbt_field_title[1] = strzone("|");
510 sbt_field[1] = SP_SEPARATOR;
511 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
513 LOG_INFO("fixed missing field '|'\n");
516 else if(!have_separator)
518 strunzone(sbt_field_title[sbt_num_fields]);
519 sbt_field_title[sbt_num_fields] = strzone("|");
520 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
521 sbt_field[sbt_num_fields] = SP_SEPARATOR;
523 LOG_INFO("fixed missing field '|'\n");
527 strunzone(sbt_field_title[sbt_num_fields]);
528 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
529 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
530 sbt_field[sbt_num_fields] = ps_secondary;
532 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
536 strunzone(sbt_field_title[sbt_num_fields]);
537 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
538 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
539 sbt_field[sbt_num_fields] = ps_primary;
541 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
545 sbt_field[sbt_num_fields] = SP_END;
549 vector sbt_field_rgb;
550 string sbt_field_icon0;
551 string sbt_field_icon1;
552 string sbt_field_icon2;
553 vector sbt_field_icon0_rgb;
554 vector sbt_field_icon1_rgb;
555 vector sbt_field_icon2_rgb;
556 float sbt_field_icon0_alpha;
557 float sbt_field_icon1_alpha;
558 float sbt_field_icon2_alpha;
559 string Scoreboard_GetField(entity pl, PlayerScoreField field)
561 float tmp, num, denom;
564 sbt_field_rgb = '1 1 1';
565 sbt_field_icon0 = "";
566 sbt_field_icon1 = "";
567 sbt_field_icon2 = "";
568 sbt_field_icon0_rgb = '1 1 1';
569 sbt_field_icon1_rgb = '1 1 1';
570 sbt_field_icon2_rgb = '1 1 1';
571 sbt_field_icon0_alpha = 1;
572 sbt_field_icon1_alpha = 1;
573 sbt_field_icon2_alpha = 1;
578 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
579 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
583 tmp = max(0, min(220, f-80)) / 220;
584 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
590 f = pl.ping_packetloss;
591 tmp = pl.ping_movementloss;
592 if(f == 0 && tmp == 0)
594 str = ftos(ceil(f * 100));
596 str = strcat(str, "~", ftos(ceil(tmp * 100)));
597 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
598 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
602 if(ready_waiting && pl.ready)
604 sbt_field_icon0 = "gfx/scoreboard/player_ready";
608 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
610 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
611 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
612 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
613 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
614 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
617 return entcs_GetName(pl.sv_entnum);
620 f = pl.(scores(SP_KILLS));
621 f -= pl.(scores(SP_SUICIDES));
625 num = pl.(scores(SP_KILLS));
626 denom = pl.(scores(SP_DEATHS));
629 sbt_field_rgb = '0 1 0';
630 str = sprintf("%d", num);
631 } else if(num <= 0) {
632 sbt_field_rgb = '1 0 0';
633 str = sprintf("%.1f", num/denom);
635 str = sprintf("%.1f", num/denom);
639 f = pl.(scores(SP_KILLS));
640 f -= pl.(scores(SP_DEATHS));
643 sbt_field_rgb = '0 1 0';
645 sbt_field_rgb = '1 1 1';
647 sbt_field_rgb = '1 0 0';
653 float elo = pl.(scores(SP_ELO));
655 case -1: return "...";
656 case -2: return _("N/A");
657 default: return ftos(elo);
662 num = pl.(scores(SP_DMG));
665 str = sprintf("%.1f k", num/denom);
669 num = pl.(scores(SP_DMGTAKEN));
672 str = sprintf("%.1f k", num/denom);
676 tmp = pl.(scores(field));
677 f = scores_flags(field);
678 if(field == ps_primary)
679 sbt_field_rgb = '1 1 0';
680 else if(field == ps_secondary)
681 sbt_field_rgb = '0 1 1';
683 sbt_field_rgb = '1 1 1';
684 return ScoreString(f, tmp);
689 float sbt_fixcolumnwidth_len;
690 float sbt_fixcolumnwidth_iconlen;
691 float sbt_fixcolumnwidth_marginlen;
693 string Scoreboard_FixColumnWidth(int i, string str)
698 PlayerScoreField field = sbt_field[i];
700 sbt_fixcolumnwidth_iconlen = 0;
702 if(sbt_field_icon0 != "")
704 sz = draw_getimagesize(sbt_field_icon0);
706 if(sbt_fixcolumnwidth_iconlen < f)
707 sbt_fixcolumnwidth_iconlen = f;
710 if(sbt_field_icon1 != "")
712 sz = draw_getimagesize(sbt_field_icon1);
714 if(sbt_fixcolumnwidth_iconlen < f)
715 sbt_fixcolumnwidth_iconlen = f;
718 if(sbt_field_icon2 != "")
720 sz = draw_getimagesize(sbt_field_icon2);
722 if(sbt_fixcolumnwidth_iconlen < f)
723 sbt_fixcolumnwidth_iconlen = f;
726 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
728 if(sbt_fixcolumnwidth_iconlen != 0)
729 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
731 sbt_fixcolumnwidth_marginlen = 0;
733 if(field == SP_NAME) // name gets all remaining space
737 namesize = panel_size.x;
738 for(j = 0; j < sbt_num_fields; ++j)
740 if (sbt_field[i] != SP_SEPARATOR)
741 namesize -= sbt_field_size[j] + hud_fontsize.x;
742 sbt_field_size[i] = namesize;
744 if (sbt_fixcolumnwidth_iconlen != 0)
745 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
746 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
747 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
750 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
752 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
753 if(sbt_field_size[i] < f)
754 sbt_field_size[i] = f;
759 vector Scoreboard_DrawHeader(vector pos, vector rgb)
762 vector column_dim = eY * panel_size.y;
763 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
764 pos.x += hud_fontsize.x * 0.5;
765 for(i = 0; i < sbt_num_fields; ++i)
767 if(sbt_field[i] == SP_SEPARATOR)
769 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
772 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
773 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
774 pos.x += column_dim.x;
776 if(sbt_field[i] == SP_SEPARATOR)
778 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
779 for(i = sbt_num_fields - 1; i > 0; --i)
781 if(sbt_field[i] == SP_SEPARATOR)
784 pos.x -= sbt_field_size[i];
789 if (i == sbt_num_fields-1)
790 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
792 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
793 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
796 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
797 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
798 pos.x -= hud_fontsize.x;
803 pos.y += 1.25 * hud_fontsize.y;
807 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
809 TC(bool, is_self); TC(int, pl_number);
811 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
812 if(is_spec && !is_self)
815 vector h_pos = item_pos;
816 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
817 // alternated rows highlighting
819 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
820 else if((sbt_highlight) && (!(pl_number % 2)))
821 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
823 vector pos = item_pos;
824 pos.x += hud_fontsize.x * 0.5;
825 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
826 vector tmp = '0 0 0';
828 PlayerScoreField field;
829 for(i = 0; i < sbt_num_fields; ++i)
831 field = sbt_field[i];
832 if(field == SP_SEPARATOR)
835 if(is_spec && field != SP_NAME && field != SP_PING) {
836 pos.x += sbt_field_size[i] + hud_fontsize.x;
839 str = Scoreboard_GetField(pl, field);
840 str = Scoreboard_FixColumnWidth(i, str);
842 pos.x += sbt_field_size[i] + hud_fontsize.x;
844 if(field == SP_NAME) {
845 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
847 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
849 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
851 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
853 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
855 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
858 tmp.x = sbt_field_size[i] + hud_fontsize.x;
859 if(sbt_field_icon0 != "")
861 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);
863 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);
864 if(sbt_field_icon1 != "")
866 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);
868 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);
869 if(sbt_field_icon2 != "")
871 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);
873 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);
876 if(sbt_field[i] == SP_SEPARATOR)
878 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
879 for(i = sbt_num_fields-1; i > 0; --i)
881 field = sbt_field[i];
882 if(field == SP_SEPARATOR)
885 if(is_spec && field != SP_NAME && field != SP_PING) {
886 pos.x -= sbt_field_size[i] + hud_fontsize.x;
890 str = Scoreboard_GetField(pl, field);
891 str = Scoreboard_FixColumnWidth(i, str);
893 if(field == SP_NAME) {
894 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
896 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
898 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
900 tmp.x = sbt_fixcolumnwidth_len;
902 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
904 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
907 tmp.x = sbt_field_size[i];
908 if(sbt_field_icon0 != "")
910 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);
912 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);
913 if(sbt_field_icon1 != "")
915 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);
917 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);
918 if(sbt_field_icon2 != "")
920 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);
922 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);
923 pos.x -= sbt_field_size[i] + hud_fontsize.x;
928 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
931 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
936 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
937 panel_size.y += panel_bg_padding * 2;
940 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
941 if(panel.current_panel_bg != "0")
942 end_pos.y += panel_bg_border * 2;
946 panel_pos += '1 1 0' * panel_bg_padding;
947 panel_size -= '2 2 0' * panel_bg_padding;
951 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
955 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
957 pos.y += 1.25 * hud_fontsize.y;
960 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
962 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
965 // print header row and highlight columns
966 pos = Scoreboard_DrawHeader(panel_pos, rgb);
968 // fill the table and draw the rows
971 for(pl = players.sort_next; pl; pl = pl.sort_next)
973 if(pl.team != tm.team)
975 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
976 pos.y += 1.25 * hud_fontsize.y;
980 for(pl = players.sort_next; pl; pl = pl.sort_next)
982 if(pl.team == NUM_SPECTATOR)
984 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
985 pos.y += 1.25 * hud_fontsize.y;
989 panel_size.x += panel_bg_padding * 2; // restore initial width
993 float Scoreboard_WouldDraw() {
994 if (QuickMenu_IsOpened())
996 else if (HUD_Radar_Clickable())
998 else if (scoreboard_showscores)
1000 else if (intermission == 1)
1002 else if (intermission == 2)
1004 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1006 else if (scoreboard_showscores_force)
1011 float average_accuracy;
1012 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1014 WepSet weapons_stat = WepSet_GetFromStat();
1015 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1016 int disownedcnt = 0;
1017 FOREACH(Weapons, it != WEP_Null, {
1018 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1020 WepSet set = it.m_wepset;
1021 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1025 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1026 if (weapon_cnt <= 0) return pos;
1029 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1031 int columnns = ceil(weapon_cnt / rows);
1035 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1036 pos.y += 1.25 * hud_fontsize.y;
1037 if(panel.current_panel_bg != "0")
1038 pos.y += panel_bg_border;
1041 panel_size.y = height * rows;
1042 panel_size.y += panel_bg_padding * 2;
1045 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1046 if(panel.current_panel_bg != "0")
1047 end_pos.y += panel_bg_border * 2;
1049 if(panel_bg_padding)
1051 panel_pos += '1 1 0' * panel_bg_padding;
1052 panel_size -= '2 2 0' * panel_bg_padding;
1056 vector tmp = panel_size;
1058 float fontsize = height * 1/3;
1059 float weapon_height = height * 2/3;
1060 float weapon_width = tmp.x / columnns / rows;
1063 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1067 // column highlighting
1068 for (int i = 0; i < columnns; ++i)
1070 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1073 for (int i = 0; i < rows; ++i)
1074 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1077 average_accuracy = 0;
1078 int weapons_with_stats = 0;
1080 pos.x += weapon_width / 2;
1082 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1085 Accuracy_LoadColors();
1087 float oldposx = pos.x;
1091 FOREACH(Weapons, it != WEP_Null, {
1092 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1094 WepSet set = it.m_wepset;
1095 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1099 if (weapon_stats >= 0)
1100 weapon_alpha = sbt_fg_alpha;
1102 weapon_alpha = 0.2 * sbt_fg_alpha;
1105 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1107 if (weapon_stats >= 0) {
1108 weapons_with_stats += 1;
1109 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1112 s = sprintf("%d%%", weapon_stats * 100);
1115 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1117 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1118 rgb = Accuracy_GetColor(weapon_stats);
1120 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1122 tmpos.x += weapon_width * rows;
1123 pos.x += weapon_width * rows;
1124 if (rows == 2 && column == columnns - 1) {
1132 if (weapons_with_stats)
1133 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1135 panel_size.x += panel_bg_padding * 2; // restore initial width
1139 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1141 pos.x += hud_fontsize.x * 0.25;
1142 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1143 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1144 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1146 pos.y += hud_fontsize.y;
1151 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1152 float stat_secrets_found, stat_secrets_total;
1153 float stat_monsters_killed, stat_monsters_total;
1157 // get monster stats
1158 stat_monsters_killed = STAT(MONSTERS_KILLED);
1159 stat_monsters_total = STAT(MONSTERS_TOTAL);
1161 // get secrets stats
1162 stat_secrets_found = STAT(SECRETS_FOUND);
1163 stat_secrets_total = STAT(SECRETS_TOTAL);
1165 // get number of rows
1166 if(stat_secrets_total)
1168 if(stat_monsters_total)
1171 // if no rows, return
1175 // draw table header
1176 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1177 pos.y += 1.25 * hud_fontsize.y;
1178 if(panel.current_panel_bg != "0")
1179 pos.y += panel_bg_border;
1182 panel_size.y = hud_fontsize.y * rows;
1183 panel_size.y += panel_bg_padding * 2;
1186 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1187 if(panel.current_panel_bg != "0")
1188 end_pos.y += panel_bg_border * 2;
1190 if(panel_bg_padding)
1192 panel_pos += '1 1 0' * panel_bg_padding;
1193 panel_size -= '2 2 0' * panel_bg_padding;
1197 vector tmp = panel_size;
1200 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1203 if(stat_monsters_total)
1205 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1206 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1210 if(stat_secrets_total)
1212 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1213 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1216 panel_size.x += panel_bg_padding * 2; // restore initial width
1221 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1224 RANKINGS_RECEIVED_CNT = 0;
1225 for (i=RANKINGS_CNT-1; i>=0; --i)
1227 ++RANKINGS_RECEIVED_CNT;
1229 if (RANKINGS_RECEIVED_CNT == 0)
1232 vector hl_rgb = rgb + '0.5 0.5 0.5';
1234 pos.y += hud_fontsize.y;
1235 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1236 pos.y += 1.25 * hud_fontsize.y;
1237 if(panel.current_panel_bg != "0")
1238 pos.y += panel_bg_border;
1241 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1242 panel_size.y += panel_bg_padding * 2;
1245 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1246 if(panel.current_panel_bg != "0")
1247 end_pos.y += panel_bg_border * 2;
1249 if(panel_bg_padding)
1251 panel_pos += '1 1 0' * panel_bg_padding;
1252 panel_size -= '2 2 0' * panel_bg_padding;
1256 vector tmp = panel_size;
1259 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1262 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1269 n = grecordholder[i];
1270 p = count_ordinal(i+1);
1271 if(grecordholder[i] == entcs_GetName(player_localnum))
1272 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1273 else if(!(i % 2) && sbt_highlight)
1274 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1275 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1276 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);
1277 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1278 pos.y += 1.25 * hud_fontsize.y;
1281 panel_size.x += panel_bg_padding * 2; // restore initial width
1285 void Scoreboard_Draw()
1287 if(!autocvar__hud_configure)
1289 // frametime checks allow to toggle the scoreboard even when the game is paused
1290 if(scoreboard_active) {
1291 if(hud_configure_menu_open == 1)
1292 scoreboard_fade_alpha = 1;
1293 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1294 if (scoreboard_fadeinspeed && frametime)
1295 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1297 scoreboard_fade_alpha = 1;
1300 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1301 if (scoreboard_fadeoutspeed && frametime)
1302 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1304 scoreboard_fade_alpha = 0;
1307 if (!scoreboard_fade_alpha)
1311 scoreboard_fade_alpha = 0;
1313 if (autocvar_hud_panel_scoreboard_dynamichud)
1316 HUD_Scale_Disable();
1318 if(scoreboard_fade_alpha <= 0)
1320 HUD_Panel_UpdateCvars(scoreboard_fade_alpha);
1322 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1323 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1324 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1325 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1326 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1327 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1329 // don't overlap with con_notify
1330 if(!autocvar__hud_configure)
1331 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1333 Scoreboard_UpdatePlayerTeams();
1339 // Initializes position
1343 vector sb_heading_fontsize;
1344 sb_heading_fontsize = hud_fontsize * 2;
1345 draw_beginBoldFont();
1346 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1349 pos.y += sb_heading_fontsize.y;
1350 if(panel.current_panel_bg != "0")
1351 pos.y += panel_bg_border;
1353 // Draw the scoreboard
1354 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1357 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1361 vector panel_bg_color_save = panel_bg_color;
1362 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1363 if(panel.current_panel_bg != "0")
1364 team_score_baseoffset.x -= panel_bg_border;
1365 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1367 if(tm.team == NUM_SPECTATOR)
1372 draw_beginBoldFont();
1373 vector rgb = Team_ColorRGB(tm.team);
1374 str = ftos(tm.(teamscores(ts_primary)));
1375 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1377 if(ts_primary != ts_secondary)
1379 str = ftos(tm.(teamscores(ts_secondary)));
1380 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);
1383 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1384 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1385 else if(panel_bg_color_team > 0)
1386 panel_bg_color = rgb * panel_bg_color_team;
1388 panel_bg_color = rgb;
1389 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1391 panel_bg_color = panel_bg_color_save;
1395 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1397 if(tm.team == NUM_SPECTATOR)
1400 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1404 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1405 if(race_speedaward) {
1406 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);
1407 pos.y += 1.25 * hud_fontsize.y;
1409 if(race_speedaward_alltimebest) {
1410 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);
1411 pos.y += 1.25 * hud_fontsize.y;
1413 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1415 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1416 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1418 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1423 for(pl = players.sort_next; pl; pl = pl.sort_next)
1425 if(pl.team != NUM_SPECTATOR)
1427 pos.y += 1.25 * hud_fontsize.y;
1428 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1434 draw_beginBoldFont();
1435 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1437 pos.y += 1.25 * hud_fontsize.y;
1440 // Print info string
1442 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1443 tl = STAT(TIMELIMIT);
1444 fl = STAT(FRAGLIMIT);
1445 ll = STAT(LEADLIMIT);
1446 if(gametype == MAPINFO_TYPE_LMS)
1449 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1454 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1458 str = strcat(str, _(" or"));
1461 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1462 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1463 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1464 TranslateScoresLabel(teamscores_label(ts_primary))));
1468 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1469 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1470 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1471 TranslateScoresLabel(scores_label(ps_primary))));
1476 if(tl > 0 || fl > 0)
1477 str = strcat(str, _(" or"));
1480 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1481 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1482 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1483 TranslateScoresLabel(teamscores_label(ts_primary))));
1487 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1488 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1489 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1490 TranslateScoresLabel(scores_label(ps_primary))));
1495 pos.y += 1.2 * hud_fontsize.y;
1496 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1498 // print information about respawn status
1499 float respawn_time = STAT(RESPAWN_TIME);
1503 if(respawn_time < 0)
1505 // a negative number means we are awaiting respawn, time value is still the same
1506 respawn_time *= -1; // remove mark now that we checked it
1508 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1509 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1511 str = sprintf(_("^1Respawning in ^3%s^1..."),
1512 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1513 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1515 count_seconds(ceil(respawn_time - time))
1519 else if(time < respawn_time)
1521 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1522 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1523 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1525 count_seconds(ceil(respawn_time - time))
1529 else if(time >= respawn_time)
1530 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1532 pos.y += 1.2 * hud_fontsize.y;
1533 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1536 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;