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 = entcs_GetClientColors(pl.sv_entnum);
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 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
792 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
795 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
796 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
797 pos.x -= hud_fontsize.x;
802 pos.y += 1.25 * hud_fontsize.y;
806 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
808 TC(bool, is_self); TC(int, pl_number);
810 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
812 vector h_pos = item_pos;
813 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
814 // alternated rows highlighting
816 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
817 else if((sbt_highlight) && (!(pl_number % 2)))
818 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
820 vector pos = item_pos;
821 pos.x += hud_fontsize.x * 0.5;
822 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
823 vector tmp = '0 0 0';
825 PlayerScoreField field;
826 for(i = 0; i < sbt_num_fields; ++i)
828 field = sbt_field[i];
829 if(field == SP_SEPARATOR)
832 if(is_spec && field != SP_NAME && field != SP_PING) {
833 pos.x += sbt_field_size[i] + hud_fontsize.x;
836 str = Scoreboard_GetField(pl, field);
837 str = Scoreboard_FixColumnWidth(i, str);
839 pos.x += sbt_field_size[i] + hud_fontsize.x;
841 if(field == SP_NAME) {
842 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
844 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
846 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
848 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
850 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
852 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
855 tmp.x = sbt_field_size[i] + hud_fontsize.x;
856 if(sbt_field_icon0 != "")
858 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);
860 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);
861 if(sbt_field_icon1 != "")
863 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);
865 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);
866 if(sbt_field_icon2 != "")
868 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);
870 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);
873 if(sbt_field[i] == SP_SEPARATOR)
875 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
876 for(i = sbt_num_fields-1; i > 0; --i)
878 field = sbt_field[i];
879 if(field == SP_SEPARATOR)
882 if(is_spec && field != SP_NAME && field != SP_PING) {
883 pos.x -= sbt_field_size[i] + hud_fontsize.x;
887 str = Scoreboard_GetField(pl, field);
888 str = Scoreboard_FixColumnWidth(i, str);
890 if(field == SP_NAME) {
891 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
893 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
895 drawcolorcodedstring(pos - tmp, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
897 tmp.x = sbt_fixcolumnwidth_len;
899 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha_self, DRAWFLAG_NORMAL);
901 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
904 tmp.x = sbt_field_size[i];
905 if(sbt_field_icon0 != "")
907 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);
909 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);
910 if(sbt_field_icon1 != "")
912 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);
914 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);
915 if(sbt_field_icon2 != "")
917 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);
919 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);
920 pos.x -= sbt_field_size[i] + hud_fontsize.x;
925 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
928 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
933 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
934 panel_size.y += panel_bg_padding * 2;
937 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
938 if(panel.current_panel_bg != "0")
939 end_pos.y += panel_bg_border * 2;
943 panel_pos += '1 1 0' * panel_bg_padding;
944 panel_size -= '2 2 0' * panel_bg_padding;
948 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
952 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
954 pos.y += 1.25 * hud_fontsize.y;
957 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
959 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
962 // print header row and highlight columns
963 pos = Scoreboard_DrawHeader(panel_pos, rgb);
965 // fill the table and draw the rows
968 for(pl = players.sort_next; pl; pl = pl.sort_next)
970 if(pl.team != tm.team)
972 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
973 pos.y += 1.25 * hud_fontsize.y;
977 for(pl = players.sort_next; pl; pl = pl.sort_next)
979 if(pl.team == NUM_SPECTATOR)
981 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
982 pos.y += 1.25 * hud_fontsize.y;
986 panel_size.x += panel_bg_padding * 2; // restore initial width
990 float Scoreboard_WouldDraw() {
991 if (QuickMenu_IsOpened())
993 else if (HUD_Radar_Clickable())
995 else if (scoreboard_showscores)
997 else if (intermission == 1)
999 else if (intermission == 2)
1001 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1003 else if (scoreboard_showscores_force)
1008 float average_accuracy;
1009 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1011 WepSet weapons_stat = WepSet_GetFromStat();
1012 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1013 int disownedcnt = 0;
1014 FOREACH(Weapons, it != WEP_Null, {
1015 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1017 WepSet set = it.m_wepset;
1018 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1022 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt;
1023 if (weapon_cnt <= 0) return pos;
1026 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - 1) * 0.5))
1028 int columnns = ceil(weapon_cnt / rows);
1032 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1033 pos.y += 1.25 * hud_fontsize.y;
1034 if(panel.current_panel_bg != "0")
1035 pos.y += panel_bg_border;
1038 panel_size.y = height * rows;
1039 panel_size.y += panel_bg_padding * 2;
1042 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1043 if(panel.current_panel_bg != "0")
1044 end_pos.y += panel_bg_border * 2;
1046 if(panel_bg_padding)
1048 panel_pos += '1 1 0' * panel_bg_padding;
1049 panel_size -= '2 2 0' * panel_bg_padding;
1053 vector tmp = panel_size;
1055 float fontsize = height * 1/3;
1056 float weapon_height = height * 2/3;
1057 float weapon_width = tmp.x / columnns / rows;
1060 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1064 // column highlighting
1065 for (int i = 0; i < columnns; ++i)
1067 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1070 for (int i = 0; i < rows; ++i)
1071 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * fontsize, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1074 average_accuracy = 0;
1075 int weapons_with_stats = 0;
1077 pos.x += weapon_width / 2;
1079 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1082 Accuracy_LoadColors();
1084 float oldposx = pos.x;
1088 FOREACH(Weapons, it != WEP_Null, {
1089 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1091 WepSet set = it.m_wepset;
1092 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1096 if (weapon_stats >= 0)
1097 weapon_alpha = sbt_fg_alpha;
1099 weapon_alpha = 0.2 * sbt_fg_alpha;
1102 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1104 if (weapon_stats >= 0) {
1105 weapons_with_stats += 1;
1106 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1109 s = sprintf("%d%%", weapon_stats * 100);
1112 padding = (weapon_width - stringwidth(s, false, eX * fontsize)) / 2; // center the accuracy value
1114 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1115 rgb = Accuracy_GetColor(weapon_stats);
1117 drawstring(tmpos + eX * padding + eY * weapon_height, s, '1 1 0' * fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1119 tmpos.x += weapon_width * rows;
1120 pos.x += weapon_width * rows;
1121 if (rows == 2 && column == columnns - 1) {
1129 if (weapons_with_stats)
1130 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1132 panel_size.x += panel_bg_padding * 2; // restore initial width
1136 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1138 pos.x += hud_fontsize.x * 0.25;
1139 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1140 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1141 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1143 pos.y += hud_fontsize.y;
1148 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1149 float stat_secrets_found, stat_secrets_total;
1150 float stat_monsters_killed, stat_monsters_total;
1154 // get monster stats
1155 stat_monsters_killed = STAT(MONSTERS_KILLED);
1156 stat_monsters_total = STAT(MONSTERS_TOTAL);
1158 // get secrets stats
1159 stat_secrets_found = STAT(SECRETS_FOUND);
1160 stat_secrets_total = STAT(SECRETS_TOTAL);
1162 // get number of rows
1163 if(stat_secrets_total)
1165 if(stat_monsters_total)
1168 // if no rows, return
1172 // draw table header
1173 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1174 pos.y += 1.25 * hud_fontsize.y;
1175 if(panel.current_panel_bg != "0")
1176 pos.y += panel_bg_border;
1179 panel_size.y = hud_fontsize.y * rows;
1180 panel_size.y += panel_bg_padding * 2;
1183 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1184 if(panel.current_panel_bg != "0")
1185 end_pos.y += panel_bg_border * 2;
1187 if(panel_bg_padding)
1189 panel_pos += '1 1 0' * panel_bg_padding;
1190 panel_size -= '2 2 0' * panel_bg_padding;
1194 vector tmp = panel_size;
1197 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1200 if(stat_monsters_total)
1202 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1203 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1207 if(stat_secrets_total)
1209 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1210 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1213 panel_size.x += panel_bg_padding * 2; // restore initial width
1218 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1221 RANKINGS_RECEIVED_CNT = 0;
1222 for (i=RANKINGS_CNT-1; i>=0; --i)
1224 ++RANKINGS_RECEIVED_CNT;
1226 if (RANKINGS_RECEIVED_CNT == 0)
1229 vector hl_rgb = rgb + '0.5 0.5 0.5';
1231 pos.y += hud_fontsize.y;
1232 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1233 pos.y += 1.25 * hud_fontsize.y;
1234 if(panel.current_panel_bg != "0")
1235 pos.y += panel_bg_border;
1238 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1239 panel_size.y += panel_bg_padding * 2;
1242 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1243 if(panel.current_panel_bg != "0")
1244 end_pos.y += panel_bg_border * 2;
1246 if(panel_bg_padding)
1248 panel_pos += '1 1 0' * panel_bg_padding;
1249 panel_size -= '2 2 0' * panel_bg_padding;
1253 vector tmp = panel_size;
1256 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1259 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1266 n = grecordholder[i];
1267 p = count_ordinal(i+1);
1268 if(grecordholder[i] == entcs_GetName(player_localnum))
1269 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1270 else if(!(i % 2) && sbt_highlight)
1271 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1272 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1273 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);
1274 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1275 pos.y += 1.25 * hud_fontsize.y;
1278 panel_size.x += panel_bg_padding * 2; // restore initial width
1282 void Scoreboard_Draw()
1284 if(!autocvar__hud_configure)
1286 if(!hud_draw_maximized) return;
1288 // frametime checks allow to toggle the scoreboard even when the game is paused
1289 if(scoreboard_active) {
1290 if(hud_configure_menu_open == 1)
1291 scoreboard_fade_alpha = 1;
1292 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1293 if (scoreboard_fadeinspeed && frametime)
1294 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1296 scoreboard_fade_alpha = 1;
1299 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1300 if (scoreboard_fadeoutspeed && frametime)
1301 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1303 scoreboard_fade_alpha = 0;
1306 if (!scoreboard_fade_alpha)
1310 scoreboard_fade_alpha = 0;
1312 if (autocvar_hud_panel_scoreboard_dynamichud)
1315 HUD_Scale_Disable();
1317 if(scoreboard_fade_alpha <= 0)
1319 panel_fade_alpha *= scoreboard_fade_alpha;
1320 HUD_Panel_LoadCvars();
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, '0 0 0', 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;