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 string autocvar_hud_fontsize;
14 string hud_fontsize_str;
18 float sbt_fg_alpha_self;
20 float sbt_highlight_alpha;
21 float sbt_highlight_alpha_self;
23 // provide basic panel cvars to old clients
24 // TODO remove them after a future release (0.8.2+)
25 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
26 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
27 string autocvar_hud_panel_scoreboard_bg = "border_default";
28 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
29 string autocvar_hud_panel_scoreboard_bg_color_team = "";
30 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
31 string autocvar_hud_panel_scoreboard_bg_border = "";
32 string autocvar_hud_panel_scoreboard_bg_padding = "";
34 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
35 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
36 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
37 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
38 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
39 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
40 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
41 bool autocvar_hud_panel_scoreboard_table_highlight = true;
42 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
43 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
44 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
46 bool autocvar_hud_panel_scoreboard_accuracy = true;
47 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
48 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
50 bool autocvar_hud_panel_scoreboard_dynamichud = false;
53 void drawstringright(vector, string, vector, vector, float, float);
54 void drawstringcenter(vector, string, vector, vector, float, float);
56 // wrapper to put all possible scores titles through gettext
57 string TranslateScoresLabel(string l)
61 case "bckills": return CTX(_("SCO^bckills"));
62 case "bctime": return CTX(_("SCO^bctime"));
63 case "caps": return CTX(_("SCO^caps"));
64 case "captime": return CTX(_("SCO^captime"));
65 case "deaths": return CTX(_("SCO^deaths"));
66 case "destroyed": return CTX(_("SCO^destroyed"));
67 case "dmg": return CTX(_("SCO^damage"));
68 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
69 case "drops": return CTX(_("SCO^drops"));
70 case "faults": return CTX(_("SCO^faults"));
71 case "fckills": return CTX(_("SCO^fckills"));
72 case "goals": return CTX(_("SCO^goals"));
73 case "kckills": return CTX(_("SCO^kckills"));
74 case "kdratio": return CTX(_("SCO^kdratio"));
75 case "kd": return CTX(_("SCO^k/d"));
76 case "kdr": return CTX(_("SCO^kdr"));
77 case "kills": return CTX(_("SCO^kills"));
78 case "laps": return CTX(_("SCO^laps"));
79 case "lives": return CTX(_("SCO^lives"));
80 case "losses": return CTX(_("SCO^losses"));
81 case "name": return CTX(_("SCO^name"));
82 case "sum": return CTX(_("SCO^sum"));
83 case "nick": return CTX(_("SCO^nick"));
84 case "objectives": return CTX(_("SCO^objectives"));
85 case "pickups": return CTX(_("SCO^pickups"));
86 case "ping": return CTX(_("SCO^ping"));
87 case "pl": return CTX(_("SCO^pl"));
88 case "pushes": return CTX(_("SCO^pushes"));
89 case "rank": return CTX(_("SCO^rank"));
90 case "returns": return CTX(_("SCO^returns"));
91 case "revivals": return CTX(_("SCO^revivals"));
92 case "rounds": return CTX(_("SCO^rounds won"));
93 case "score": return CTX(_("SCO^score"));
94 case "suicides": return CTX(_("SCO^suicides"));
95 case "takes": return CTX(_("SCO^takes"));
96 case "ticks": return CTX(_("SCO^ticks"));
101 void Scoreboard_InitScores()
105 ps_primary = ps_secondary = NULL;
106 ts_primary = ts_secondary = -1;
107 FOREACH(Scores, true, {
108 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
109 if(f == SFL_SORT_PRIO_PRIMARY)
111 if(f == SFL_SORT_PRIO_SECONDARY)
114 if(ps_secondary == NULL)
115 ps_secondary = ps_primary;
117 for(i = 0; i < MAX_TEAMSCORE; ++i)
119 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
120 if(f == SFL_SORT_PRIO_PRIMARY)
122 if(f == SFL_SORT_PRIO_SECONDARY)
125 if(ts_secondary == -1)
126 ts_secondary = ts_primary;
128 Cmd_Scoreboard_SetFields(0);
131 float SetTeam(entity pl, float Team);
133 void Scoreboard_UpdatePlayerTeams()
140 for(pl = players.sort_next; pl; pl = pl.sort_next)
143 Team = entcs_GetScoreTeam(pl.sv_entnum);
144 if(SetTeam(pl, Team))
147 Scoreboard_UpdatePlayerPos(pl);
151 pl = players.sort_next;
156 print(strcat("PNUM: ", ftos(num), "\n"));
161 int Scoreboard_CompareScore(int vl, int vr, int f)
163 TC(int, vl); TC(int, vr); TC(int, f);
164 if(f & SFL_ZERO_IS_WORST)
166 if(vl == 0 && vr != 0)
168 if(vl != 0 && vr == 0)
172 return IS_INCREASING(f);
174 return IS_DECREASING(f);
178 float Scoreboard_ComparePlayerScores(entity left, entity right)
181 vl = entcs_GetTeam(left.sv_entnum);
182 vr = entcs_GetTeam(right.sv_entnum);
194 if(vl == NUM_SPECTATOR)
196 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
198 if(!left.gotscores && right.gotscores)
203 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
207 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
211 FOREACH(Scores, true, {
212 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
213 if (r >= 0) return r;
216 if (left.sv_entnum < right.sv_entnum)
222 void Scoreboard_UpdatePlayerPos(entity player)
225 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
227 SORT_SWAP(player, ent);
229 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
231 SORT_SWAP(ent, player);
235 float Scoreboard_CompareTeamScores(entity left, entity right)
239 if(left.team == NUM_SPECTATOR)
241 if(right.team == NUM_SPECTATOR)
244 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
248 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
252 for(i = 0; i < MAX_TEAMSCORE; ++i)
254 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
259 if (left.team < right.team)
265 void Scoreboard_UpdateTeamPos(entity Team)
268 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
270 SORT_SWAP(Team, ent);
272 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
274 SORT_SWAP(ent, Team);
278 void Cmd_Scoreboard_Help()
280 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
281 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
282 LOG_INFO(_("Usage:\n"));
283 LOG_INFO(_("^2scoreboard_columns_set default\n"));
284 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
285 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
286 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
289 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
290 LOG_INFO(_("^3ping^7 Ping time\n"));
291 LOG_INFO(_("^3pl^7 Packet loss\n"));
292 LOG_INFO(_("^3elo^7 Player ELO\n"));
293 LOG_INFO(_("^3kills^7 Number of kills\n"));
294 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
295 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
296 LOG_INFO(_("^3frags^7 kills - suicides\n"));
297 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
298 LOG_INFO(_("^3dmg^7 The total damage done\n"));
299 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
300 LOG_INFO(_("^3sum^7 frags - deaths\n"));
301 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
302 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
303 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
304 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
305 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
306 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
307 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
308 LOG_INFO(_("^3rank^7 Player rank\n"));
309 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
310 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
311 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
312 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
313 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
314 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
315 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
316 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
317 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
318 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
319 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
320 LOG_INFO(_("^3score^7 Total score\n"));
323 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
324 "of game types, then a slash, to make the field show up only in these\n"
325 "or in all but these game types. You can also specify 'all' as a\n"
326 "field to show all fields available for the current game mode.\n\n"));
328 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
329 "include/exclude ALL teams/noteams game modes.\n\n"));
331 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
332 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
333 "right of the vertical bar aligned to the right.\n"));
334 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
335 "other gamemodes except DM.\n"));
338 // NOTE: adding a gametype with ? to not warn for an optional field
339 // make sure it's excluded in a previous exclusive rule, if any
340 // otherwise the previous exclusive rule warns anyway
341 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
342 #define SCOREBOARD_DEFAULT_COLUMNS \
344 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
345 " -teams,lms/deaths +ft,tdm/deaths" \
346 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
347 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
348 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
349 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
350 " +lms/lives +lms/rank" \
351 " +kh/caps +kh/pushes +kh/destroyed" \
352 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
353 " +as/objectives +nb/faults +nb/goals" \
354 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
355 " -lms,rc,cts,inv,nb/score"
357 void Cmd_Scoreboard_SetFields(int argc)
362 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
366 return; // do nothing, we don't know gametype and scores yet
368 // sbt_fields uses strunzone on the titles!
369 if(!sbt_field_title[0])
370 for(i = 0; i < MAX_SBT_FIELDS; ++i)
371 sbt_field_title[i] = strzone("(null)");
373 // TODO: re enable with gametype dependant cvars?
374 if(argc < 3) // no arguments provided
375 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
378 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
382 if(argv(2) == "default")
383 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
384 else if(argv(2) == "all")
387 s = "ping pl name |";
388 FOREACH(Scores, true, {
390 if(it != ps_secondary)
391 if(scores_label(it) != "")
392 s = strcat(s, " ", scores_label(it));
394 if(ps_secondary != ps_primary)
395 s = strcat(s, " ", scores_label(ps_secondary));
396 s = strcat(s, " ", scores_label(ps_primary));
397 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
404 hud_fontsize = HUD_GetFontsize("hud_fontsize");
406 for(i = 1; i < argc - 1; ++i)
412 if(substring(str, 0, 1) == "?")
415 str = substring(str, 1, strlen(str) - 1);
418 slash = strstrofs(str, "/", 0);
421 pattern = substring(str, 0, slash);
422 str = substring(str, slash + 1, strlen(str) - (slash + 1));
424 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
428 strunzone(sbt_field_title[sbt_num_fields]);
429 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
430 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
431 str = strtolower(str);
436 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
437 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
438 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
439 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
440 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
441 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
442 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
443 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
444 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
447 FOREACH(Scores, true, {
448 if (str == strtolower(scores_label(it))) {
450 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
460 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
464 sbt_field[sbt_num_fields] = j;
467 if(j == ps_secondary)
473 if(sbt_num_fields >= MAX_SBT_FIELDS)
477 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
479 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
481 if(ps_primary == ps_secondary)
483 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
485 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
489 strunzone(sbt_field_title[sbt_num_fields]);
490 for(i = sbt_num_fields; i > 0; --i)
492 sbt_field_title[i] = sbt_field_title[i-1];
493 sbt_field_size[i] = sbt_field_size[i-1];
494 sbt_field[i] = sbt_field[i-1];
496 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
497 sbt_field[0] = SP_NAME;
499 LOG_INFO("fixed missing field 'name'\n");
503 strunzone(sbt_field_title[sbt_num_fields]);
504 for(i = sbt_num_fields; i > 1; --i)
506 sbt_field_title[i] = sbt_field_title[i-1];
507 sbt_field_size[i] = sbt_field_size[i-1];
508 sbt_field[i] = sbt_field[i-1];
510 sbt_field_title[1] = strzone("|");
511 sbt_field[1] = SP_SEPARATOR;
512 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
514 LOG_INFO("fixed missing field '|'\n");
517 else if(!have_separator)
519 strunzone(sbt_field_title[sbt_num_fields]);
520 sbt_field_title[sbt_num_fields] = strzone("|");
521 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
522 sbt_field[sbt_num_fields] = SP_SEPARATOR;
524 LOG_INFO("fixed missing field '|'\n");
528 strunzone(sbt_field_title[sbt_num_fields]);
529 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
530 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
531 sbt_field[sbt_num_fields] = ps_secondary;
533 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
537 strunzone(sbt_field_title[sbt_num_fields]);
538 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
539 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
540 sbt_field[sbt_num_fields] = ps_primary;
542 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
546 sbt_field[sbt_num_fields] = SP_END;
550 vector sbt_field_rgb;
551 string sbt_field_icon0;
552 string sbt_field_icon1;
553 string sbt_field_icon2;
554 vector sbt_field_icon0_rgb;
555 vector sbt_field_icon1_rgb;
556 vector sbt_field_icon2_rgb;
557 string Scoreboard_GetField(entity pl, PlayerScoreField field)
559 float tmp, num, denom;
562 sbt_field_rgb = '1 1 1';
563 sbt_field_icon0 = "";
564 sbt_field_icon1 = "";
565 sbt_field_icon2 = "";
566 sbt_field_icon0_rgb = '1 1 1';
567 sbt_field_icon1_rgb = '1 1 1';
568 sbt_field_icon2_rgb = '1 1 1';
573 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
574 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
578 tmp = max(0, min(220, f-80)) / 220;
579 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
585 f = pl.ping_packetloss;
586 tmp = pl.ping_movementloss;
587 if(f == 0 && tmp == 0)
589 str = ftos(ceil(f * 100));
591 str = strcat(str, "~", ftos(ceil(tmp * 100)));
592 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
593 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
597 if(ready_waiting && pl.ready)
599 sbt_field_icon0 = "gfx/scoreboard/player_ready";
603 f = entcs_GetClientColors(pl.sv_entnum);
605 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
606 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
607 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
608 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
609 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
612 return entcs_GetName(pl.sv_entnum);
615 f = pl.(scores(SP_KILLS));
616 f -= pl.(scores(SP_SUICIDES));
620 num = pl.(scores(SP_KILLS));
621 denom = pl.(scores(SP_DEATHS));
624 sbt_field_rgb = '0 1 0';
625 str = sprintf("%d", num);
626 } else if(num <= 0) {
627 sbt_field_rgb = '1 0 0';
628 str = sprintf("%.1f", num/denom);
630 str = sprintf("%.1f", num/denom);
634 f = pl.(scores(SP_KILLS));
635 f -= pl.(scores(SP_DEATHS));
638 sbt_field_rgb = '0 1 0';
640 sbt_field_rgb = '1 1 1';
642 sbt_field_rgb = '1 0 0';
648 float elo = pl.(scores(SP_ELO));
650 case -1: return "...";
651 case -2: return _("N/A");
652 default: return ftos(elo);
656 case SP_DMG: case SP_DMGTAKEN:
657 return sprintf("%.1f k", pl.(scores(field)) / 1000);
660 tmp = pl.(scores(field));
661 f = scores_flags(field);
662 if(field == ps_primary)
663 sbt_field_rgb = '1 1 0';
664 else if(field == ps_secondary)
665 sbt_field_rgb = '0 1 1';
667 sbt_field_rgb = '1 1 1';
668 return ScoreString(f, tmp);
673 float sbt_fixcolumnwidth_len;
674 float sbt_fixcolumnwidth_iconlen;
675 float sbt_fixcolumnwidth_marginlen;
677 string Scoreboard_FixColumnWidth(int i, string str)
683 sbt_fixcolumnwidth_iconlen = 0;
685 if(sbt_field_icon0 != "")
687 sz = draw_getimagesize(sbt_field_icon0);
689 if(sbt_fixcolumnwidth_iconlen < f)
690 sbt_fixcolumnwidth_iconlen = f;
693 if(sbt_field_icon1 != "")
695 sz = draw_getimagesize(sbt_field_icon1);
697 if(sbt_fixcolumnwidth_iconlen < f)
698 sbt_fixcolumnwidth_iconlen = f;
701 if(sbt_field_icon2 != "")
703 sz = draw_getimagesize(sbt_field_icon2);
705 if(sbt_fixcolumnwidth_iconlen < f)
706 sbt_fixcolumnwidth_iconlen = f;
709 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
711 if(sbt_fixcolumnwidth_iconlen != 0)
712 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
714 sbt_fixcolumnwidth_marginlen = 0;
716 if(sbt_field[i] == SP_NAME) // name gets all remaining space
720 namesize = panel_size.x;
721 for(j = 0; j < sbt_num_fields; ++j)
723 if (sbt_field[i] != SP_SEPARATOR)
724 namesize -= sbt_field_size[j] + hud_fontsize.x;
725 sbt_field_size[i] = namesize;
727 if (sbt_fixcolumnwidth_iconlen != 0)
728 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
729 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
730 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
733 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
735 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
736 if(sbt_field_size[i] < f)
737 sbt_field_size[i] = f;
742 void Scoreboard_initFieldSizes()
744 for(int i = 0; i < sbt_num_fields; ++i)
745 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
748 vector Scoreboard_DrawHeader(vector pos, vector rgb)
751 vector column_dim = eY * panel_size.y;
752 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
753 pos.x += hud_fontsize.x * 0.5;
754 for(i = 0; i < sbt_num_fields; ++i)
756 if(sbt_field[i] == SP_SEPARATOR)
758 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
761 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
762 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
763 pos.x += column_dim.x;
765 if(sbt_field[i] == SP_SEPARATOR)
767 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
768 for(i = sbt_num_fields - 1; i > 0; --i)
770 if(sbt_field[i] == SP_SEPARATOR)
773 pos.x -= sbt_field_size[i];
778 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
779 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
782 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
783 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
784 pos.x -= hud_fontsize.x;
789 pos.y += 1.25 * hud_fontsize.y;
793 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
795 TC(bool, is_self); TC(int, pl_number);
797 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
799 vector h_pos = item_pos;
800 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
801 // alternated rows highlighting
803 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
804 else if((sbt_highlight) && (!(pl_number % 2)))
805 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
807 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
809 vector pos = item_pos;
810 pos.x += hud_fontsize.x * 0.5;
811 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
812 vector tmp = '0 0 0';
814 PlayerScoreField field;
815 for(i = 0; i < sbt_num_fields; ++i)
817 field = sbt_field[i];
818 if(field == SP_SEPARATOR)
821 if(is_spec && field != SP_NAME && field != SP_PING) {
822 pos.x += sbt_field_size[i] + hud_fontsize.x;
825 str = Scoreboard_GetField(pl, field);
826 str = Scoreboard_FixColumnWidth(i, str);
828 pos.x += sbt_field_size[i] + hud_fontsize.x;
830 if(field == SP_NAME) {
831 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
832 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
834 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
835 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
838 tmp.x = sbt_field_size[i] + hud_fontsize.x;
839 if(sbt_field_icon0 != "")
840 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
841 if(sbt_field_icon1 != "")
842 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
843 if(sbt_field_icon2 != "")
844 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
847 if(sbt_field[i] == SP_SEPARATOR)
849 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
850 for(i = sbt_num_fields-1; i > 0; --i)
852 field = sbt_field[i];
853 if(field == SP_SEPARATOR)
856 if(is_spec && field != SP_NAME && field != SP_PING) {
857 pos.x -= sbt_field_size[i] + hud_fontsize.x;
861 str = Scoreboard_GetField(pl, field);
862 str = Scoreboard_FixColumnWidth(i, str);
864 if(field == SP_NAME) {
865 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
866 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
868 tmp.x = sbt_fixcolumnwidth_len;
869 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
872 tmp.x = sbt_field_size[i];
873 if(sbt_field_icon0 != "")
874 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
875 if(sbt_field_icon1 != "")
876 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
877 if(sbt_field_icon2 != "")
878 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
879 pos.x -= sbt_field_size[i] + hud_fontsize.x;
884 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
887 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
892 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
893 panel_size.y += panel_bg_padding * 2;
896 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
897 if(panel.current_panel_bg != "0")
898 end_pos.y += panel_bg_border * 2;
902 panel_pos += '1 1 0' * panel_bg_padding;
903 panel_size -= '2 2 0' * panel_bg_padding;
907 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
911 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
913 pos.y += 1.25 * hud_fontsize.y;
916 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
918 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
921 // print header row and highlight columns
922 pos = Scoreboard_DrawHeader(panel_pos, rgb);
924 // fill the table and draw the rows
927 for(pl = players.sort_next; pl; pl = pl.sort_next)
929 if(pl.team != tm.team)
931 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
932 pos.y += 1.25 * hud_fontsize.y;
936 for(pl = players.sort_next; pl; pl = pl.sort_next)
938 if(pl.team == NUM_SPECTATOR)
940 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
941 pos.y += 1.25 * hud_fontsize.y;
945 panel_size.x += panel_bg_padding * 2; // restore initial width
949 float Scoreboard_WouldDraw() {
950 if (QuickMenu_IsOpened())
952 else if (HUD_Radar_Clickable())
954 else if (scoreboard_showscores)
956 else if (intermission == 1)
958 else if (intermission == 2)
960 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
962 else if (scoreboard_showscores_force)
967 float average_accuracy;
968 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
970 WepSet weapons_stat = WepSet_GetFromStat();
971 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
974 FOREACH(Weapons, it != WEP_Null, {
975 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
977 WepSet set = it.m_wepset;
978 if (weapon_stats < 0)
980 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
982 else if (!(weapons_stat & set || weapons_inmap & set))
987 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
988 if (weapon_cnt <= 0) return pos;
991 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
993 int columnns = ceil(weapon_cnt / rows);
995 float weapon_height = 29;
996 float height = hud_fontsize.y + weapon_height;
998 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
999 pos.y += 1.25 * hud_fontsize.y;
1000 if(panel.current_panel_bg != "0")
1001 pos.y += panel_bg_border;
1004 panel_size.y = height * rows;
1005 panel_size.y += panel_bg_padding * 2;
1008 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1009 if(panel.current_panel_bg != "0")
1010 end_pos.y += panel_bg_border * 2;
1012 if(panel_bg_padding)
1014 panel_pos += '1 1 0' * panel_bg_padding;
1015 panel_size -= '2 2 0' * panel_bg_padding;
1019 vector tmp = panel_size;
1021 float weapon_width = tmp.x / columnns / rows;
1024 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1028 // column highlighting
1029 for (int i = 0; i < columnns; ++i)
1031 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1034 for (int i = 0; i < rows; ++i)
1035 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1038 average_accuracy = 0;
1039 int weapons_with_stats = 0;
1041 pos.x += weapon_width / 2;
1043 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1046 Accuracy_LoadColors();
1048 float oldposx = pos.x;
1052 FOREACH(Weapons, it != WEP_Null, {
1053 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1055 WepSet set = it.m_wepset;
1056 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1060 if (weapon_stats >= 0)
1061 weapon_alpha = sbt_fg_alpha;
1063 weapon_alpha = 0.2 * sbt_fg_alpha;
1066 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1068 if (weapon_stats >= 0) {
1069 weapons_with_stats += 1;
1070 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1073 s = sprintf("%d%%", weapon_stats * 100);
1076 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1078 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1079 rgb = Accuracy_GetColor(weapon_stats);
1081 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1083 tmpos.x += weapon_width * rows;
1084 pos.x += weapon_width * rows;
1085 if (rows == 2 && column == columnns - 1) {
1093 if (weapons_with_stats)
1094 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1096 panel_size.x += panel_bg_padding * 2; // restore initial width
1100 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1102 pos.x += hud_fontsize.x * 0.25;
1103 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1104 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1105 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1107 pos.y += hud_fontsize.y;
1112 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1113 float stat_secrets_found, stat_secrets_total;
1114 float stat_monsters_killed, stat_monsters_total;
1118 // get monster stats
1119 stat_monsters_killed = STAT(MONSTERS_KILLED);
1120 stat_monsters_total = STAT(MONSTERS_TOTAL);
1122 // get secrets stats
1123 stat_secrets_found = STAT(SECRETS_FOUND);
1124 stat_secrets_total = STAT(SECRETS_TOTAL);
1126 // get number of rows
1127 if(stat_secrets_total)
1129 if(stat_monsters_total)
1132 // if no rows, return
1136 // draw table header
1137 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1138 pos.y += 1.25 * hud_fontsize.y;
1139 if(panel.current_panel_bg != "0")
1140 pos.y += panel_bg_border;
1143 panel_size.y = hud_fontsize.y * rows;
1144 panel_size.y += panel_bg_padding * 2;
1147 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1148 if(panel.current_panel_bg != "0")
1149 end_pos.y += panel_bg_border * 2;
1151 if(panel_bg_padding)
1153 panel_pos += '1 1 0' * panel_bg_padding;
1154 panel_size -= '2 2 0' * panel_bg_padding;
1158 vector tmp = panel_size;
1161 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1164 if(stat_monsters_total)
1166 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1167 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1171 if(stat_secrets_total)
1173 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1174 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1177 panel_size.x += panel_bg_padding * 2; // restore initial width
1182 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1185 RANKINGS_RECEIVED_CNT = 0;
1186 for (i=RANKINGS_CNT-1; i>=0; --i)
1188 ++RANKINGS_RECEIVED_CNT;
1190 if (RANKINGS_RECEIVED_CNT == 0)
1193 vector hl_rgb = rgb + '0.5 0.5 0.5';
1195 pos.y += hud_fontsize.y;
1196 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1197 pos.y += 1.25 * hud_fontsize.y;
1198 if(panel.current_panel_bg != "0")
1199 pos.y += panel_bg_border;
1202 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1203 panel_size.y += panel_bg_padding * 2;
1206 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1207 if(panel.current_panel_bg != "0")
1208 end_pos.y += panel_bg_border * 2;
1210 if(panel_bg_padding)
1212 panel_pos += '1 1 0' * panel_bg_padding;
1213 panel_size -= '2 2 0' * panel_bg_padding;
1217 vector tmp = panel_size;
1220 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1223 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1230 n = grecordholder[i];
1231 p = count_ordinal(i+1);
1232 if(grecordholder[i] == entcs_GetName(player_localnum))
1233 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1234 else if(!(i % 2) && sbt_highlight)
1235 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1236 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1237 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);
1238 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1239 pos.y += 1.25 * hud_fontsize.y;
1242 panel_size.x += panel_bg_padding * 2; // restore initial width
1246 void Scoreboard_Draw()
1248 if(!autocvar__hud_configure)
1250 if(!hud_draw_maximized) return;
1252 // frametime checks allow to toggle the scoreboard even when the game is paused
1253 if(scoreboard_active) {
1254 if(hud_configure_menu_open == 1)
1255 scoreboard_fade_alpha = 1;
1256 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1257 if (scoreboard_fadeinspeed && frametime)
1258 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1260 scoreboard_fade_alpha = 1;
1261 if(hud_fontsize_str != autocvar_hud_fontsize)
1263 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1264 Scoreboard_initFieldSizes();
1265 if(hud_fontsize_str)
1266 strunzone(hud_fontsize_str);
1267 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1271 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1272 if (scoreboard_fadeoutspeed && frametime)
1273 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1275 scoreboard_fade_alpha = 0;
1278 if (!scoreboard_fade_alpha)
1282 scoreboard_fade_alpha = 0;
1284 if (autocvar_hud_panel_scoreboard_dynamichud)
1287 HUD_Scale_Disable();
1289 if(scoreboard_fade_alpha <= 0)
1291 panel_fade_alpha *= scoreboard_fade_alpha;
1292 HUD_Panel_LoadCvars();
1294 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1295 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1296 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1297 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1298 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1299 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1301 // don't overlap with con_notify
1302 if(!autocvar__hud_configure)
1303 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1305 Scoreboard_UpdatePlayerTeams();
1311 // Initializes position
1315 vector sb_heading_fontsize;
1316 sb_heading_fontsize = hud_fontsize * 2;
1317 draw_beginBoldFont();
1318 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1321 pos.y += sb_heading_fontsize.y;
1322 if(panel.current_panel_bg != "0")
1323 pos.y += panel_bg_border;
1325 // Draw the scoreboard
1326 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1329 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1333 vector panel_bg_color_save = panel_bg_color;
1334 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1335 if(panel.current_panel_bg != "0")
1336 team_score_baseoffset.x -= panel_bg_border;
1337 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1339 if(tm.team == NUM_SPECTATOR)
1344 draw_beginBoldFont();
1345 vector rgb = Team_ColorRGB(tm.team);
1346 str = ftos(tm.(teamscores(ts_primary)));
1347 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1349 if(ts_primary != ts_secondary)
1351 str = ftos(tm.(teamscores(ts_secondary)));
1352 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);
1355 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1356 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1357 else if(panel_bg_color_team > 0)
1358 panel_bg_color = rgb * panel_bg_color_team;
1360 panel_bg_color = rgb;
1361 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1363 panel_bg_color = panel_bg_color_save;
1367 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1369 if(tm.team == NUM_SPECTATOR)
1372 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1376 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1377 if(race_speedaward) {
1378 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);
1379 pos.y += 1.25 * hud_fontsize.y;
1381 if(race_speedaward_alltimebest) {
1382 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);
1383 pos.y += 1.25 * hud_fontsize.y;
1385 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1387 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1388 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1390 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1395 for(pl = players.sort_next; pl; pl = pl.sort_next)
1397 if(pl.team != NUM_SPECTATOR)
1399 pos.y += 1.25 * hud_fontsize.y;
1400 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1406 draw_beginBoldFont();
1407 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1409 pos.y += 1.25 * hud_fontsize.y;
1412 // Print info string
1414 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1415 tl = STAT(TIMELIMIT);
1416 fl = STAT(FRAGLIMIT);
1417 ll = STAT(LEADLIMIT);
1418 if(gametype == MAPINFO_TYPE_LMS)
1421 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1426 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1430 str = strcat(str, _(" or"));
1433 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1434 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1435 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1436 TranslateScoresLabel(teamscores_label(ts_primary))));
1440 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1441 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1442 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1443 TranslateScoresLabel(scores_label(ps_primary))));
1448 if(tl > 0 || fl > 0)
1449 str = strcat(str, _(" or"));
1452 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1453 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1454 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1455 TranslateScoresLabel(teamscores_label(ts_primary))));
1459 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1460 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1461 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1462 TranslateScoresLabel(scores_label(ps_primary))));
1467 pos.y += 1.2 * hud_fontsize.y;
1468 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1470 // print information about respawn status
1471 float respawn_time = STAT(RESPAWN_TIME);
1475 if(respawn_time < 0)
1477 // a negative number means we are awaiting respawn, time value is still the same
1478 respawn_time *= -1; // remove mark now that we checked it
1480 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1481 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1483 str = sprintf(_("^1Respawning in ^3%s^1..."),
1484 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1485 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1487 count_seconds(ceil(respawn_time - time))
1491 else if(time < respawn_time)
1493 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1494 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1495 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1497 count_seconds(ceil(respawn_time - time))
1501 else if(time >= respawn_time)
1502 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1504 pos.y += 1.2 * hud_fontsize.y;
1505 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1508 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;