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);
813 vector h_pos = item_pos;
814 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
815 // alternated rows highlighting
817 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
818 else if((sbt_highlight) && (!(pl_number % 2)))
819 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
821 vector pos = item_pos;
822 pos.x += hud_fontsize.x * 0.5;
823 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
824 vector tmp = '0 0 0';
826 PlayerScoreField field;
827 for(i = 0; i < sbt_num_fields; ++i)
829 field = sbt_field[i];
830 if(field == SP_SEPARATOR)
833 if(is_spec && field != SP_NAME && field != SP_PING) {
834 pos.x += sbt_field_size[i] + hud_fontsize.x;
837 str = Scoreboard_GetField(pl, field);
838 str = Scoreboard_FixColumnWidth(i, str);
840 pos.x += sbt_field_size[i] + hud_fontsize.x;
842 if(field == SP_NAME) {
843 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
845 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
847 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
849 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
851 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
853 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
856 tmp.x = sbt_field_size[i] + hud_fontsize.x;
857 if(sbt_field_icon0 != "")
859 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);
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, DRAWFLAG_NORMAL);
862 if(sbt_field_icon1 != "")
864 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);
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, DRAWFLAG_NORMAL);
867 if(sbt_field_icon2 != "")
869 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);
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, DRAWFLAG_NORMAL);
874 if(sbt_field[i] == SP_SEPARATOR)
876 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
877 for(i = sbt_num_fields-1; i > 0; --i)
879 field = sbt_field[i];
880 if(field == SP_SEPARATOR)
883 if(is_spec && field != SP_NAME && field != SP_PING) {
884 pos.x -= sbt_field_size[i] + hud_fontsize.x;
888 str = Scoreboard_GetField(pl, field);
889 str = Scoreboard_FixColumnWidth(i, str);
891 if(field == SP_NAME) {
892 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
894 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
896 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
898 tmp.x = sbt_fixcolumnwidth_len;
900 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
902 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
905 tmp.x = sbt_field_size[i];
906 if(sbt_field_icon0 != "")
908 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);
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, DRAWFLAG_NORMAL);
911 if(sbt_field_icon1 != "")
913 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);
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, DRAWFLAG_NORMAL);
916 if(sbt_field_icon2 != "")
918 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);
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, DRAWFLAG_NORMAL);
921 pos.x -= sbt_field_size[i] + hud_fontsize.x;
926 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
929 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
934 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
935 panel_size.y += panel_bg_padding * 2;
936 HUD_Panel_DrawBg(scoreboard_fade_alpha);
938 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
939 if(panel.current_panel_bg != "0")
940 end_pos.y += panel_bg_border * 2;
944 panel_pos += '1 1 0' * panel_bg_padding;
945 panel_size -= '2 2 0' * panel_bg_padding;
949 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
953 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
955 pos.y += 1.25 * hud_fontsize.y;
958 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
960 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
963 // print header row and highlight columns
964 pos = Scoreboard_DrawHeader(panel_pos, rgb);
966 // fill the table and draw the rows
969 for(pl = players.sort_next; pl; pl = pl.sort_next)
971 if(pl.team != tm.team)
973 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
974 pos.y += 1.25 * hud_fontsize.y;
978 for(pl = players.sort_next; pl; pl = pl.sort_next)
980 if(pl.team == NUM_SPECTATOR)
982 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
983 pos.y += 1.25 * hud_fontsize.y;
987 panel_size.x += panel_bg_padding * 2; // restore initial width
991 float Scoreboard_WouldDraw() {
992 if (QuickMenu_IsOpened())
994 else if (HUD_Radar_Clickable())
996 else if (scoreboard_showscores)
998 else if (intermission == 1)
1000 else if (intermission == 2)
1002 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1004 else if (scoreboard_showscores_force)
1009 float average_accuracy;
1010 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1012 WepSet weapons_stat = WepSet_GetFromStat();
1013 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1014 int disownedcnt = 0;
1015 FOREACH(Weapons, it != WEP_Null, {
1016 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1018 WepSet set = it.m_wepset;
1019 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1023 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1024 if (weapon_cnt <= 0) return pos;
1027 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1029 int columnns = ceil(weapon_cnt / rows);
1033 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1034 pos.y += 1.25 * hud_fontsize.y;
1035 if(panel.current_panel_bg != "0")
1036 pos.y += panel_bg_border;
1039 panel_size.y = height * rows;
1040 panel_size.y += panel_bg_padding * 2;
1041 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1043 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1044 if(panel.current_panel_bg != "0")
1045 end_pos.y += panel_bg_border * 2;
1047 if(panel_bg_padding)
1049 panel_pos += '1 1 0' * panel_bg_padding;
1050 panel_size -= '2 2 0' * panel_bg_padding;
1054 vector tmp = panel_size;
1056 float fontsize = height * 1/3;
1057 float weapon_height = height * 2/3;
1058 float weapon_width = tmp.x / columnns / rows;
1061 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1065 // column highlighting
1066 for (int i = 0; i < columnns; ++i)
1068 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1071 for (int i = 0; i < rows; ++i)
1072 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1075 average_accuracy = 0;
1076 int weapons_with_stats = 0;
1078 pos.x += weapon_width / 2;
1080 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1083 Accuracy_LoadColors();
1085 float oldposx = pos.x;
1089 FOREACH(Weapons, it != WEP_Null, {
1090 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1092 WepSet set = it.m_wepset;
1093 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1097 if (weapon_stats >= 0)
1098 weapon_alpha = sbt_fg_alpha;
1100 weapon_alpha = 0.2 * sbt_fg_alpha;
1103 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1105 if (weapon_stats >= 0) {
1106 weapons_with_stats += 1;
1107 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1110 s = sprintf("%d%%", weapon_stats * 100);
1113 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1115 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1116 rgb = Accuracy_GetColor(weapon_stats);
1118 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1120 tmpos.x += weapon_width * rows;
1121 pos.x += weapon_width * rows;
1122 if (rows == 2 && column == columnns - 1) {
1130 if (weapons_with_stats)
1131 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1133 panel_size.x += panel_bg_padding * 2; // restore initial width
1137 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1139 pos.x += hud_fontsize.x * 0.25;
1140 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1141 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1142 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1144 pos.y += hud_fontsize.y;
1149 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1150 float stat_secrets_found, stat_secrets_total;
1151 float stat_monsters_killed, stat_monsters_total;
1155 // get monster stats
1156 stat_monsters_killed = STAT(MONSTERS_KILLED);
1157 stat_monsters_total = STAT(MONSTERS_TOTAL);
1159 // get secrets stats
1160 stat_secrets_found = STAT(SECRETS_FOUND);
1161 stat_secrets_total = STAT(SECRETS_TOTAL);
1163 // get number of rows
1164 if(stat_secrets_total)
1166 if(stat_monsters_total)
1169 // if no rows, return
1173 // draw table header
1174 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1175 pos.y += 1.25 * hud_fontsize.y;
1176 if(panel.current_panel_bg != "0")
1177 pos.y += panel_bg_border;
1180 panel_size.y = hud_fontsize.y * rows;
1181 panel_size.y += panel_bg_padding * 2;
1182 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1184 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1185 if(panel.current_panel_bg != "0")
1186 end_pos.y += panel_bg_border * 2;
1188 if(panel_bg_padding)
1190 panel_pos += '1 1 0' * panel_bg_padding;
1191 panel_size -= '2 2 0' * panel_bg_padding;
1195 vector tmp = panel_size;
1198 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1201 if(stat_monsters_total)
1203 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1204 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1208 if(stat_secrets_total)
1210 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1211 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1214 panel_size.x += panel_bg_padding * 2; // restore initial width
1219 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1222 RANKINGS_RECEIVED_CNT = 0;
1223 for (i=RANKINGS_CNT-1; i>=0; --i)
1225 ++RANKINGS_RECEIVED_CNT;
1227 if (RANKINGS_RECEIVED_CNT == 0)
1230 vector hl_rgb = rgb + '0.5 0.5 0.5';
1232 pos.y += hud_fontsize.y;
1233 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1234 pos.y += 1.25 * hud_fontsize.y;
1235 if(panel.current_panel_bg != "0")
1236 pos.y += panel_bg_border;
1239 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1240 panel_size.y += panel_bg_padding * 2;
1241 HUD_Panel_DrawBg(scoreboard_fade_alpha);
1243 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1244 if(panel.current_panel_bg != "0")
1245 end_pos.y += panel_bg_border * 2;
1247 if(panel_bg_padding)
1249 panel_pos += '1 1 0' * panel_bg_padding;
1250 panel_size -= '2 2 0' * panel_bg_padding;
1254 vector tmp = panel_size;
1257 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1260 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1267 n = grecordholder[i];
1268 p = count_ordinal(i+1);
1269 if(grecordholder[i] == entcs_GetName(player_localnum))
1270 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1271 else if(!(i % 2) && sbt_highlight)
1272 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1273 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1274 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);
1275 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1276 pos.y += 1.25 * hud_fontsize.y;
1279 panel_size.x += panel_bg_padding * 2; // restore initial width
1283 void Scoreboard_Draw()
1285 if(!autocvar__hud_configure)
1287 // frametime checks allow to toggle the scoreboard even when the game is paused
1288 if(scoreboard_active) {
1289 if(hud_configure_menu_open == 1)
1290 scoreboard_fade_alpha = 1;
1291 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1292 if (scoreboard_fadeinspeed && frametime)
1293 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1295 scoreboard_fade_alpha = 1;
1298 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1299 if (scoreboard_fadeoutspeed && frametime)
1300 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1302 scoreboard_fade_alpha = 0;
1305 if (!scoreboard_fade_alpha)
1309 scoreboard_fade_alpha = 0;
1311 if (autocvar_hud_panel_scoreboard_dynamichud)
1314 HUD_Scale_Disable();
1316 float hud_fade_alpha_save = hud_fade_alpha;
1317 if(hud_configure_menu_open == 1)
1320 hud_fade_alpha = scoreboard_fade_alpha * (1 - autocvar__menu_alpha);
1321 HUD_Panel_UpdateCvars();
1323 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1324 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1325 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1326 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1327 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1328 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1330 hud_fade_alpha = hud_fade_alpha_save;
1332 // don't overlap with con_notify
1333 if(!autocvar__hud_configure)
1334 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1336 Scoreboard_UpdatePlayerTeams();
1342 // Initializes position
1346 vector sb_heading_fontsize;
1347 sb_heading_fontsize = hud_fontsize * 2;
1348 draw_beginBoldFont();
1349 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1352 pos.y += sb_heading_fontsize.y;
1353 if(panel.current_panel_bg != "0")
1354 pos.y += panel_bg_border;
1356 // Draw the scoreboard
1357 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1360 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1364 vector panel_bg_color_save = panel_bg_color;
1365 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1366 if(panel.current_panel_bg != "0")
1367 team_score_baseoffset.x -= panel_bg_border;
1368 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1370 if(tm.team == NUM_SPECTATOR)
1375 draw_beginBoldFont();
1376 vector rgb = Team_ColorRGB(tm.team);
1377 str = ftos(tm.(teamscores(ts_primary)));
1378 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1380 if(ts_primary != ts_secondary)
1382 str = ftos(tm.(teamscores(ts_secondary)));
1383 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);
1386 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1387 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1388 else if(panel_bg_color_team > 0)
1389 panel_bg_color = rgb * panel_bg_color_team;
1391 panel_bg_color = rgb;
1392 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1394 panel_bg_color = panel_bg_color_save;
1398 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1400 if(tm.team == NUM_SPECTATOR)
1403 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1407 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1408 if(race_speedaward) {
1409 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);
1410 pos.y += 1.25 * hud_fontsize.y;
1412 if(race_speedaward_alltimebest) {
1413 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);
1414 pos.y += 1.25 * hud_fontsize.y;
1416 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1418 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1419 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1421 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1426 for(pl = players.sort_next; pl; pl = pl.sort_next)
1428 if(pl.team != NUM_SPECTATOR)
1430 pos.y += 1.25 * hud_fontsize.y;
1431 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1437 draw_beginBoldFont();
1438 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1440 pos.y += 1.25 * hud_fontsize.y;
1443 // Print info string
1445 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1446 tl = STAT(TIMELIMIT);
1447 fl = STAT(FRAGLIMIT);
1448 ll = STAT(LEADLIMIT);
1449 if(gametype == MAPINFO_TYPE_LMS)
1452 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1457 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1461 str = strcat(str, _(" or"));
1464 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1465 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1466 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1467 TranslateScoresLabel(teamscores_label(ts_primary))));
1471 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1472 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1473 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1474 TranslateScoresLabel(scores_label(ps_primary))));
1479 if(tl > 0 || fl > 0)
1480 str = strcat(str, _(" or"));
1483 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1484 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1485 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1486 TranslateScoresLabel(teamscores_label(ts_primary))));
1490 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1491 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1492 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1493 TranslateScoresLabel(scores_label(ps_primary))));
1498 pos.y += 1.2 * hud_fontsize.y;
1499 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1501 // print information about respawn status
1502 float respawn_time = STAT(RESPAWN_TIME);
1506 if(respawn_time < 0)
1508 // a negative number means we are awaiting respawn, time value is still the same
1509 respawn_time *= -1; // remove mark now that we checked it
1511 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1512 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1514 str = sprintf(_("^1Respawning in ^3%s^1..."),
1515 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1516 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1518 count_seconds(ceil(respawn_time - time))
1522 else if(time < respawn_time)
1524 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1525 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1526 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1528 count_seconds(ceil(respawn_time - time))
1532 else if(time >= respawn_time)
1533 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1535 pos.y += 1.2 * hud_fontsize.y;
1536 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1539 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;