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^dmg"));
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 "k/d": return CTX(_("SCO^k/d"));
73 case "kd": return CTX(_("SCO^kd"));
74 case "kdr": return CTX(_("SCO^kdr"));
75 case "kills": return CTX(_("SCO^kills"));
76 case "laps": return CTX(_("SCO^laps"));
77 case "lives": return CTX(_("SCO^lives"));
78 case "losses": return CTX(_("SCO^losses"));
79 case "name": return CTX(_("SCO^name"));
80 case "sum": return CTX(_("SCO^sum"));
81 case "nick": return CTX(_("SCO^nick"));
82 case "objectives": return CTX(_("SCO^objectives"));
83 case "pickups": return CTX(_("SCO^pickups"));
84 case "ping": return CTX(_("SCO^ping"));
85 case "pl": return CTX(_("SCO^pl"));
86 case "pushes": return CTX(_("SCO^pushes"));
87 case "rank": return CTX(_("SCO^rank"));
88 case "returns": return CTX(_("SCO^returns"));
89 case "revivals": return CTX(_("SCO^revivals"));
90 case "rounds": return CTX(_("SCO^rounds won"));
91 case "score": return CTX(_("SCO^score"));
92 case "suicides": return CTX(_("SCO^suicides"));
93 case "takes": return CTX(_("SCO^takes"));
94 case "ticks": return CTX(_("SCO^ticks"));
99 void Scoreboard_InitScores()
103 ps_primary = ps_secondary = NULL;
104 ts_primary = ts_secondary = -1;
105 FOREACH(Scores, true, {
106 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
107 if(f == SFL_SORT_PRIO_PRIMARY)
109 if(f == SFL_SORT_PRIO_SECONDARY)
112 if(ps_secondary == NULL)
113 ps_secondary = ps_primary;
115 for(i = 0; i < MAX_TEAMSCORE; ++i)
117 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
118 if(f == SFL_SORT_PRIO_PRIMARY)
120 if(f == SFL_SORT_PRIO_SECONDARY)
123 if(ts_secondary == -1)
124 ts_secondary = ts_primary;
126 Cmd_Scoreboard_SetFields(0);
129 float SetTeam(entity pl, float Team);
131 void Scoreboard_UpdatePlayerTeams()
138 for(pl = players.sort_next; pl; pl = pl.sort_next)
141 Team = entcs_GetScoreTeam(pl.sv_entnum);
142 if(SetTeam(pl, Team))
145 Scoreboard_UpdatePlayerPos(pl);
149 pl = players.sort_next;
154 print(strcat("PNUM: ", ftos(num), "\n"));
159 int Scoreboard_CompareScore(int vl, int vr, int f)
161 TC(int, vl); TC(int, vr); TC(int, f);
162 if(f & SFL_ZERO_IS_WORST)
164 if(vl == 0 && vr != 0)
166 if(vl != 0 && vr == 0)
170 return IS_INCREASING(f);
172 return IS_DECREASING(f);
176 float Scoreboard_ComparePlayerScores(entity left, entity right)
179 vl = entcs_GetTeam(left.sv_entnum);
180 vr = entcs_GetTeam(right.sv_entnum);
192 if(vl == NUM_SPECTATOR)
194 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
196 if(!left.gotscores && right.gotscores)
201 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
205 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
209 FOREACH(Scores, true, {
210 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
211 if (r >= 0) return r;
214 if (left.sv_entnum < right.sv_entnum)
220 void Scoreboard_UpdatePlayerPos(entity player)
223 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
225 SORT_SWAP(player, ent);
227 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
229 SORT_SWAP(ent, player);
233 float Scoreboard_CompareTeamScores(entity left, entity right)
237 if(left.team == NUM_SPECTATOR)
239 if(right.team == NUM_SPECTATOR)
242 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
246 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
250 for(i = 0; i < MAX_TEAMSCORE; ++i)
252 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
257 if (left.team < right.team)
263 void Scoreboard_UpdateTeamPos(entity Team)
266 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
268 SORT_SWAP(Team, ent);
270 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
272 SORT_SWAP(ent, Team);
276 void Cmd_Scoreboard_Help()
278 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
279 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
280 LOG_INFO(_("Usage:\n"));
281 LOG_INFO(_("^2scoreboard_columns_set default\n"));
282 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
283 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
284 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
287 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
288 LOG_INFO(_("^3ping^7 Ping time\n"));
289 LOG_INFO(_("^3pl^7 Packet loss\n"));
290 LOG_INFO(_("^3elo^7 Player ELO\n"));
291 LOG_INFO(_("^3kills^7 Number of kills\n"));
292 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
293 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
294 LOG_INFO(_("^3frags^7 kills - suicides\n"));
295 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
296 LOG_INFO(_("^3dmg^7 The total damage done\n"));
297 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
298 LOG_INFO(_("^3sum^7 frags - deaths\n"));
299 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
300 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
301 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
302 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
303 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
304 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
305 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
306 LOG_INFO(_("^3rank^7 Player rank\n"));
307 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
308 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
309 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
310 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
311 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
312 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
313 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
314 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
315 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
316 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
317 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
318 LOG_INFO(_("^3score^7 Total score\n"));
321 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
322 "of game types, then a slash, to make the field show up only in these\n"
323 "or in all but these game types. You can also specify 'all' as a\n"
324 "field to show all fields available for the current game mode.\n\n"));
326 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
327 "include/exclude ALL teams/noteams game modes.\n\n"));
329 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
330 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
331 "right of the vertical bar aligned to the right.\n"));
332 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
333 "other gamemodes except DM.\n"));
336 // NOTE: adding a gametype with ? to not warn for an optional field
337 // make sure it's excluded in a previous exclusive rule, if any
338 // otherwise the previous exclusive rule warns anyway
339 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
340 #define SCOREBOARD_DEFAULT_COLUMNS \
342 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
343 " -teams,lms/deaths +ft,tdm/deaths" \
344 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
345 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
346 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
347 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
348 " +lms/lives +lms/rank" \
349 " +kh/caps +kh/pushes +kh/destroyed" \
350 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
351 " +as/objectives +nb/faults +nb/goals" \
352 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
353 " -lms,rc,cts,inv,nb/score"
355 void Cmd_Scoreboard_SetFields(int argc)
360 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
365 // set up a temporary scoreboard layout
366 // no layout can be properly set up until score_info data haven't been received
367 argc = tokenizebyseparator("0 1 ping pl name | score", " ");
368 ps_primary = SP_SCORE;
369 ps_secondary = SP_SCORE;
370 scores_label(ps_primary) = strzone("score");
371 scores_flags(ps_primary) = SFL_ALLOW_HIDE;
374 // TODO: re enable with gametype dependant cvars?
375 if(argc < 3) // no arguments provided
376 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
379 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
383 if(argv(2) == "default")
384 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
385 else if(argv(2) == "all")
388 s = "ping pl name |";
389 FOREACH(Scores, true, {
391 if(it != ps_secondary)
392 if(scores_label(it) != "")
393 s = strcat(s, " ", scores_label(it));
395 if(ps_secondary != ps_primary)
396 s = strcat(s, " ", scores_label(ps_secondary));
397 s = strcat(s, " ", scores_label(ps_primary));
398 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
405 hud_fontsize = HUD_GetFontsize("hud_fontsize");
407 for(i = 1; i < argc - 1; ++i)
413 if(substring(str, 0, 1) == "?")
416 str = substring(str, 1, strlen(str) - 1);
419 slash = strstrofs(str, "/", 0);
422 pattern = substring(str, 0, slash);
423 str = substring(str, slash + 1, strlen(str) - (slash + 1));
425 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
429 strunzone(sbt_field_title[sbt_num_fields]);
430 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
431 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
432 str = strtolower(str);
437 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
438 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
439 case "kd": case "kdr": case "kdratio": case "k/d": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
440 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
441 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
442 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
443 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
444 case "dmg": sbt_field[sbt_num_fields] = SP_DMG; break;
445 case "dmgtaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
448 FOREACH(Scores, true, {
449 if (str == strtolower(scores_label(it))) {
451 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
461 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
465 sbt_field[sbt_num_fields] = j;
468 if(j == ps_secondary)
474 if(sbt_num_fields >= MAX_SBT_FIELDS)
478 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
480 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
482 if(ps_primary == ps_secondary)
484 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
486 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
490 strunzone(sbt_field_title[sbt_num_fields]);
491 for(i = sbt_num_fields; i > 0; --i)
493 sbt_field_title[i] = sbt_field_title[i-1];
494 sbt_field_size[i] = sbt_field_size[i-1];
495 sbt_field[i] = sbt_field[i-1];
497 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
498 sbt_field[0] = SP_NAME;
500 LOG_INFO("fixed missing field 'name'\n");
504 strunzone(sbt_field_title[sbt_num_fields]);
505 for(i = sbt_num_fields; i > 1; --i)
507 sbt_field_title[i] = sbt_field_title[i-1];
508 sbt_field_size[i] = sbt_field_size[i-1];
509 sbt_field[i] = sbt_field[i-1];
511 sbt_field_title[1] = strzone("|");
512 sbt_field[1] = SP_SEPARATOR;
513 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
515 LOG_INFO("fixed missing field '|'\n");
518 else if(!have_separator)
520 strunzone(sbt_field_title[sbt_num_fields]);
521 sbt_field_title[sbt_num_fields] = strzone("|");
522 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
523 sbt_field[sbt_num_fields] = SP_SEPARATOR;
525 LOG_INFO("fixed missing field '|'\n");
529 strunzone(sbt_field_title[sbt_num_fields]);
530 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
531 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
532 sbt_field[sbt_num_fields] = ps_secondary;
534 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
538 strunzone(sbt_field_title[sbt_num_fields]);
539 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
540 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
541 sbt_field[sbt_num_fields] = ps_primary;
543 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
547 sbt_field[sbt_num_fields] = SP_END;
551 vector sbt_field_rgb;
552 string sbt_field_icon0;
553 string sbt_field_icon1;
554 string sbt_field_icon2;
555 vector sbt_field_icon0_rgb;
556 vector sbt_field_icon1_rgb;
557 vector sbt_field_icon2_rgb;
558 float sbt_field_icon0_alpha;
559 float sbt_field_icon1_alpha;
560 float sbt_field_icon2_alpha;
561 string Scoreboard_GetField(entity pl, PlayerScoreField field)
563 float tmp, num, denom;
566 sbt_field_rgb = '1 1 1';
567 sbt_field_icon0 = "";
568 sbt_field_icon1 = "";
569 sbt_field_icon2 = "";
570 sbt_field_icon0_rgb = '1 1 1';
571 sbt_field_icon1_rgb = '1 1 1';
572 sbt_field_icon2_rgb = '1 1 1';
573 sbt_field_icon0_alpha = 1;
574 sbt_field_icon1_alpha = 1;
575 sbt_field_icon2_alpha = 1;
580 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
581 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
585 tmp = max(0, min(220, f-80)) / 220;
586 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
592 f = pl.ping_packetloss;
593 tmp = pl.ping_movementloss;
594 if(f == 0 && tmp == 0)
596 str = ftos(ceil(f * 100));
598 str = strcat(str, "~", ftos(ceil(tmp * 100)));
599 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
600 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
604 if(ready_waiting && pl.ready)
606 sbt_field_icon0 = "gfx/scoreboard/player_ready";
610 f = stof(getplayerkeyvalue(pl.sv_entnum, "colors"));
612 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
613 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
614 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
615 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
616 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
619 return entcs_GetName(pl.sv_entnum);
622 f = pl.(scores(SP_KILLS));
623 f -= pl.(scores(SP_SUICIDES));
627 num = pl.(scores(SP_KILLS));
628 denom = pl.(scores(SP_DEATHS));
631 sbt_field_rgb = '0 1 0';
632 str = sprintf("%d", num);
633 } else if(num <= 0) {
634 sbt_field_rgb = '1 0 0';
635 str = sprintf("%.1f", num/denom);
637 str = sprintf("%.1f", num/denom);
641 f = pl.(scores(SP_KILLS));
642 f -= pl.(scores(SP_DEATHS));
645 sbt_field_rgb = '0 1 0';
647 sbt_field_rgb = '1 1 1';
649 sbt_field_rgb = '1 0 0';
655 float elo = pl.(scores(SP_ELO));
657 case -1: return "...";
658 case -2: return _("N/A");
659 default: return ftos(elo);
664 num = pl.(scores(SP_DMG));
667 str = sprintf("%.1f k", num/denom);
671 num = pl.(scores(SP_DMGTAKEN));
674 str = sprintf("%.1f k", num/denom);
678 tmp = pl.(scores(field));
679 f = scores_flags(field);
680 if(field == ps_primary)
681 sbt_field_rgb = '1 1 0';
682 else if(field == ps_secondary)
683 sbt_field_rgb = '0 1 1';
685 sbt_field_rgb = '1 1 1';
686 return ScoreString(f, tmp);
691 float sbt_fixcolumnwidth_len;
692 float sbt_fixcolumnwidth_iconlen;
693 float sbt_fixcolumnwidth_marginlen;
695 string Scoreboard_FixColumnWidth(int i, string str)
700 PlayerScoreField field = sbt_field[i];
702 sbt_fixcolumnwidth_iconlen = 0;
704 if(sbt_field_icon0 != "")
706 sz = draw_getimagesize(sbt_field_icon0);
708 if(sbt_fixcolumnwidth_iconlen < f)
709 sbt_fixcolumnwidth_iconlen = f;
712 if(sbt_field_icon1 != "")
714 sz = draw_getimagesize(sbt_field_icon1);
716 if(sbt_fixcolumnwidth_iconlen < f)
717 sbt_fixcolumnwidth_iconlen = f;
720 if(sbt_field_icon2 != "")
722 sz = draw_getimagesize(sbt_field_icon2);
724 if(sbt_fixcolumnwidth_iconlen < f)
725 sbt_fixcolumnwidth_iconlen = f;
728 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
730 if(sbt_fixcolumnwidth_iconlen != 0)
731 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
733 sbt_fixcolumnwidth_marginlen = 0;
735 if(field == SP_NAME) // name gets all remaining space
739 namesize = panel_size.x;
740 for(j = 0; j < sbt_num_fields; ++j)
742 if (sbt_field[i] != SP_SEPARATOR)
743 namesize -= sbt_field_size[j] + hud_fontsize.x;
744 sbt_field_size[i] = namesize;
746 if (sbt_fixcolumnwidth_iconlen != 0)
747 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
748 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
749 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
752 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
754 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
755 if(sbt_field_size[i] < f)
756 sbt_field_size[i] = f;
761 vector Scoreboard_DrawHeader(vector pos, vector rgb)
764 vector column_dim = eY * panel_size.y;
765 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
766 pos.x += hud_fontsize.x * 0.5;
767 for(i = 0; i < sbt_num_fields; ++i)
769 if(sbt_field[i] == SP_SEPARATOR)
771 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
774 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
775 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
776 pos.x += column_dim.x;
778 if(sbt_field[i] == SP_SEPARATOR)
780 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
781 for(i = sbt_num_fields - 1; i > 0; --i)
783 if(sbt_field[i] == SP_SEPARATOR)
786 pos.x -= sbt_field_size[i];
791 if (i == sbt_num_fields-1)
792 column_dim.x = sbt_field_size[i] + hud_fontsize.x * 0.5;
794 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
795 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
798 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
799 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
800 pos.x -= hud_fontsize.x;
805 pos.y += 1.25 * hud_fontsize.y;
809 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
811 TC(bool, is_self); TC(int, pl_number);
813 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
814 if(is_spec && !is_self)
817 vector h_pos = item_pos;
818 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
819 // alternated rows highlighting
821 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
822 else if((sbt_highlight) && (!(pl_number % 2)))
823 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
825 vector pos = item_pos;
826 pos.x += hud_fontsize.x * 0.5;
827 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
828 vector tmp = '0 0 0';
830 PlayerScoreField field;
831 for(i = 0; i < sbt_num_fields; ++i)
833 field = sbt_field[i];
834 if(field == SP_SEPARATOR)
837 if(is_spec && field != SP_NAME && field != SP_PING) {
838 pos.x += sbt_field_size[i] + hud_fontsize.x;
841 str = Scoreboard_GetField(pl, field);
842 str = Scoreboard_FixColumnWidth(i, str);
844 pos.x += sbt_field_size[i] + hud_fontsize.x;
846 if(field == SP_NAME) {
847 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
849 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
851 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
855 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
857 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
860 tmp.x = sbt_field_size[i] + hud_fontsize.x;
861 if(sbt_field_icon0 != "")
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_self, DRAWFLAG_NORMAL);
865 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);
866 if(sbt_field_icon1 != "")
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_self, DRAWFLAG_NORMAL);
870 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);
871 if(sbt_field_icon2 != "")
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_self, DRAWFLAG_NORMAL);
875 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);
878 if(sbt_field[i] == SP_SEPARATOR)
880 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
881 for(i = sbt_num_fields-1; i > 0; --i)
883 field = sbt_field[i];
884 if(field == SP_SEPARATOR)
887 if(is_spec && field != SP_NAME && field != SP_PING) {
888 pos.x -= sbt_field_size[i] + hud_fontsize.x;
892 str = Scoreboard_GetField(pl, field);
893 str = Scoreboard_FixColumnWidth(i, str);
895 if(field == SP_NAME) {
896 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
898 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
900 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
902 tmp.x = sbt_fixcolumnwidth_len;
904 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
906 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
909 tmp.x = sbt_field_size[i];
910 if(sbt_field_icon0 != "")
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_self, DRAWFLAG_NORMAL);
914 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);
915 if(sbt_field_icon1 != "")
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_self, DRAWFLAG_NORMAL);
919 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);
920 if(sbt_field_icon2 != "")
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_self, DRAWFLAG_NORMAL);
924 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);
925 pos.x -= sbt_field_size[i] + hud_fontsize.x;
930 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
933 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
938 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
939 panel_size.y += panel_bg_padding * 2;
942 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
943 if(panel.current_panel_bg != "0")
944 end_pos.y += panel_bg_border * 2;
948 panel_pos += '1 1 0' * panel_bg_padding;
949 panel_size -= '2 2 0' * panel_bg_padding;
953 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
957 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
959 pos.y += 1.25 * hud_fontsize.y;
962 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
964 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
967 // print header row and highlight columns
968 pos = Scoreboard_DrawHeader(panel_pos, rgb);
970 // fill the table and draw the rows
973 for(pl = players.sort_next; pl; pl = pl.sort_next)
975 if(pl.team != tm.team)
977 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
978 pos.y += 1.25 * hud_fontsize.y;
982 for(pl = players.sort_next; pl; pl = pl.sort_next)
984 if(pl.team == NUM_SPECTATOR)
986 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
987 pos.y += 1.25 * hud_fontsize.y;
991 panel_size.x += panel_bg_padding * 2; // restore initial width
995 float Scoreboard_WouldDraw() {
996 if (QuickMenu_IsOpened())
998 else if (HUD_Radar_Clickable())
1000 else if (scoreboard_showscores)
1002 else if (intermission == 1)
1004 else if (intermission == 2)
1006 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1008 else if (scoreboard_showscores_force)
1013 float average_accuracy;
1014 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1016 WepSet weapons_stat = WepSet_GetFromStat();
1017 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1018 int disownedcnt = 0;
1019 FOREACH(Weapons, it != WEP_Null, {
1020 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1022 WepSet set = it.m_wepset;
1023 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1027 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1028 if (weapon_cnt <= 0) return pos;
1031 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1033 int columnns = ceil(weapon_cnt / rows);
1037 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1038 pos.y += 1.25 * hud_fontsize.y;
1039 if(panel.current_panel_bg != "0")
1040 pos.y += panel_bg_border;
1043 panel_size.y = height * rows;
1044 panel_size.y += panel_bg_padding * 2;
1047 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1048 if(panel.current_panel_bg != "0")
1049 end_pos.y += panel_bg_border * 2;
1051 if(panel_bg_padding)
1053 panel_pos += '1 1 0' * panel_bg_padding;
1054 panel_size -= '2 2 0' * panel_bg_padding;
1058 vector tmp = panel_size;
1060 float fontsize = height * 1/3;
1061 float weapon_height = height * 2/3;
1062 float weapon_width = tmp.x / columnns / rows;
1065 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1069 // column highlighting
1070 for (int i = 0; i < columnns; ++i)
1072 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1075 for (int i = 0; i < rows; ++i)
1076 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1079 average_accuracy = 0;
1080 int weapons_with_stats = 0;
1082 pos.x += weapon_width / 2;
1084 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1087 Accuracy_LoadColors();
1089 float oldposx = pos.x;
1093 FOREACH(Weapons, it != WEP_Null, {
1094 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1096 WepSet set = it.m_wepset;
1097 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1101 if (weapon_stats >= 0)
1102 weapon_alpha = sbt_fg_alpha;
1104 weapon_alpha = 0.2 * sbt_fg_alpha;
1107 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1109 if (weapon_stats >= 0) {
1110 weapons_with_stats += 1;
1111 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1114 s = sprintf("%d%%", weapon_stats * 100);
1117 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1119 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1120 rgb = Accuracy_GetColor(weapon_stats);
1122 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1124 tmpos.x += weapon_width * rows;
1125 pos.x += weapon_width * rows;
1126 if (rows == 2 && column == columnns - 1) {
1134 if (weapons_with_stats)
1135 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1137 panel_size.x += panel_bg_padding * 2; // restore initial width
1141 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1143 pos.x += hud_fontsize.x * 0.25;
1144 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1145 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1146 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1148 pos.y += hud_fontsize.y;
1153 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1154 float stat_secrets_found, stat_secrets_total;
1155 float stat_monsters_killed, stat_monsters_total;
1159 // get monster stats
1160 stat_monsters_killed = STAT(MONSTERS_KILLED);
1161 stat_monsters_total = STAT(MONSTERS_TOTAL);
1163 // get secrets stats
1164 stat_secrets_found = STAT(SECRETS_FOUND);
1165 stat_secrets_total = STAT(SECRETS_TOTAL);
1167 // get number of rows
1168 if(stat_secrets_total)
1170 if(stat_monsters_total)
1173 // if no rows, return
1177 // draw table header
1178 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1179 pos.y += 1.25 * hud_fontsize.y;
1180 if(panel.current_panel_bg != "0")
1181 pos.y += panel_bg_border;
1184 panel_size.y = hud_fontsize.y * rows;
1185 panel_size.y += panel_bg_padding * 2;
1188 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1189 if(panel.current_panel_bg != "0")
1190 end_pos.y += panel_bg_border * 2;
1192 if(panel_bg_padding)
1194 panel_pos += '1 1 0' * panel_bg_padding;
1195 panel_size -= '2 2 0' * panel_bg_padding;
1199 vector tmp = panel_size;
1202 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1205 if(stat_monsters_total)
1207 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1208 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1212 if(stat_secrets_total)
1214 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1215 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1218 panel_size.x += panel_bg_padding * 2; // restore initial width
1223 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1226 RANKINGS_RECEIVED_CNT = 0;
1227 for (i=RANKINGS_CNT-1; i>=0; --i)
1229 ++RANKINGS_RECEIVED_CNT;
1231 if (RANKINGS_RECEIVED_CNT == 0)
1234 vector hl_rgb = rgb + '0.5 0.5 0.5';
1236 pos.y += hud_fontsize.y;
1237 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1238 pos.y += 1.25 * hud_fontsize.y;
1239 if(panel.current_panel_bg != "0")
1240 pos.y += panel_bg_border;
1243 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1244 panel_size.y += panel_bg_padding * 2;
1247 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1248 if(panel.current_panel_bg != "0")
1249 end_pos.y += panel_bg_border * 2;
1251 if(panel_bg_padding)
1253 panel_pos += '1 1 0' * panel_bg_padding;
1254 panel_size -= '2 2 0' * panel_bg_padding;
1258 vector tmp = panel_size;
1261 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1264 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1271 n = grecordholder[i];
1272 p = count_ordinal(i+1);
1273 if(grecordholder[i] == entcs_GetName(player_localnum))
1274 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1275 else if(!(i % 2) && sbt_highlight)
1276 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1277 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1278 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);
1279 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1280 pos.y += 1.25 * hud_fontsize.y;
1283 panel_size.x += panel_bg_padding * 2; // restore initial width
1287 void Scoreboard_Draw()
1289 if(!autocvar__hud_configure)
1291 if(!hud_draw_maximized) return;
1293 // frametime checks allow to toggle the scoreboard even when the game is paused
1294 if(scoreboard_active) {
1295 if(hud_configure_menu_open == 1)
1296 scoreboard_fade_alpha = 1;
1297 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1298 if (scoreboard_fadeinspeed && frametime)
1299 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1301 scoreboard_fade_alpha = 1;
1304 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1305 if (scoreboard_fadeoutspeed && frametime)
1306 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1308 scoreboard_fade_alpha = 0;
1311 if (!scoreboard_fade_alpha)
1315 scoreboard_fade_alpha = 0;
1317 if (autocvar_hud_panel_scoreboard_dynamichud)
1320 HUD_Scale_Disable();
1322 if(scoreboard_fade_alpha <= 0)
1324 panel_fade_alpha *= scoreboard_fade_alpha;
1325 HUD_Panel_LoadCvars();
1327 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1328 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1329 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1330 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1331 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1332 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1334 // don't overlap with con_notify
1335 if(!autocvar__hud_configure)
1336 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1338 Scoreboard_UpdatePlayerTeams();
1344 // Initializes position
1348 vector sb_heading_fontsize;
1349 sb_heading_fontsize = hud_fontsize * 2;
1350 draw_beginBoldFont();
1351 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1354 pos.y += sb_heading_fontsize.y;
1355 if(panel.current_panel_bg != "0")
1356 pos.y += panel_bg_border;
1358 // Draw the scoreboard
1359 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1362 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1366 vector panel_bg_color_save = panel_bg_color;
1367 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1368 if(panel.current_panel_bg != "0")
1369 team_score_baseoffset.x -= panel_bg_border;
1370 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1372 if(tm.team == NUM_SPECTATOR)
1377 draw_beginBoldFont();
1378 vector rgb = Team_ColorRGB(tm.team);
1379 str = ftos(tm.(teamscores(ts_primary)));
1380 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1382 if(ts_primary != ts_secondary)
1384 str = ftos(tm.(teamscores(ts_secondary)));
1385 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);
1388 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1389 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1390 else if(panel_bg_color_team > 0)
1391 panel_bg_color = rgb * panel_bg_color_team;
1393 panel_bg_color = rgb;
1394 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1396 panel_bg_color = panel_bg_color_save;
1400 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1402 if(tm.team == NUM_SPECTATOR)
1405 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1409 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1410 if(race_speedaward) {
1411 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);
1412 pos.y += 1.25 * hud_fontsize.y;
1414 if(race_speedaward_alltimebest) {
1415 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);
1416 pos.y += 1.25 * hud_fontsize.y;
1418 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1420 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1421 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1423 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1428 for(pl = players.sort_next; pl; pl = pl.sort_next)
1430 if(pl.team != NUM_SPECTATOR)
1432 pos.y += 1.25 * hud_fontsize.y;
1433 Scoreboard_DrawItem(pos, panel_bg_color, pl, (pl.sv_entnum == player_localnum), specs);
1439 draw_beginBoldFont();
1440 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1442 pos.y += 1.25 * hud_fontsize.y;
1445 // Print info string
1447 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1448 tl = STAT(TIMELIMIT);
1449 fl = STAT(FRAGLIMIT);
1450 ll = STAT(LEADLIMIT);
1451 if(gametype == MAPINFO_TYPE_LMS)
1454 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1459 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1463 str = strcat(str, _(" or"));
1466 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1467 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1468 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1469 TranslateScoresLabel(teamscores_label(ts_primary))));
1473 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1474 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1475 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1476 TranslateScoresLabel(scores_label(ps_primary))));
1481 if(tl > 0 || fl > 0)
1482 str = strcat(str, _(" or"));
1485 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1486 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1487 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1488 TranslateScoresLabel(teamscores_label(ts_primary))));
1492 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1493 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1494 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1495 TranslateScoresLabel(scores_label(ps_primary))));
1500 pos.y += 1.2 * hud_fontsize.y;
1501 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1503 // print information about respawn status
1504 float respawn_time = STAT(RESPAWN_TIME);
1508 if(respawn_time < 0)
1510 // a negative number means we are awaiting respawn, time value is still the same
1511 respawn_time *= -1; // remove mark now that we checked it
1513 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1514 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1516 str = sprintf(_("^1Respawning in ^3%s^1..."),
1517 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1518 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1520 count_seconds(ceil(respawn_time - time))
1524 else if(time < respawn_time)
1526 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1527 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1528 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1530 count_seconds(ceil(respawn_time - time))
1534 else if(time >= respawn_time)
1535 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1537 pos.y += 1.2 * hud_fontsize.y;
1538 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1541 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;