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 const int MAX_SBT_FIELDS = MAX_SCORE;
15 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
16 float sbt_field_size[MAX_SBT_FIELDS + 1];
17 string sbt_field_title[MAX_SBT_FIELDS + 1];
20 string autocvar_hud_fontsize;
21 string hud_fontsize_str;
25 float sbt_fg_alpha_self;
27 float sbt_highlight_alpha;
28 float sbt_highlight_alpha_self;
30 // provide basic panel cvars to old clients
31 // TODO remove them after a future release (0.8.2+)
32 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
33 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
34 string autocvar_hud_panel_scoreboard_bg = "border_default";
35 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
36 string autocvar_hud_panel_scoreboard_bg_color_team = "";
37 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
38 string autocvar_hud_panel_scoreboard_bg_border = "";
39 string autocvar_hud_panel_scoreboard_bg_padding = "";
41 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
42 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
43 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
44 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
45 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
46 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
48 bool autocvar_hud_panel_scoreboard_table_highlight = true;
49 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
51 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 bool autocvar_hud_panel_scoreboard_accuracy = true;
54 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
55 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
57 bool autocvar_hud_panel_scoreboard_dynamichud = false;
60 void drawstringright(vector, string, vector, vector, float, float);
61 void drawstringcenter(vector, string, vector, vector, float, float);
63 // wrapper to put all possible scores titles through gettext
64 string TranslateScoresLabel(string l)
68 case "bckills": return CTX(_("SCO^bckills"));
69 case "bctime": return CTX(_("SCO^bctime"));
70 case "caps": return CTX(_("SCO^caps"));
71 case "captime": return CTX(_("SCO^captime"));
72 case "deaths": return CTX(_("SCO^deaths"));
73 case "destroyed": return CTX(_("SCO^destroyed"));
74 case "dmg": return CTX(_("SCO^damage"));
75 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
76 case "drops": return CTX(_("SCO^drops"));
77 case "faults": return CTX(_("SCO^faults"));
78 case "fckills": return CTX(_("SCO^fckills"));
79 case "goals": return CTX(_("SCO^goals"));
80 case "kckills": return CTX(_("SCO^kckills"));
81 case "kdratio": return CTX(_("SCO^kdratio"));
82 case "kd": return CTX(_("SCO^k/d"));
83 case "kdr": return CTX(_("SCO^kdr"));
84 case "kills": return CTX(_("SCO^kills"));
85 case "laps": return CTX(_("SCO^laps"));
86 case "lives": return CTX(_("SCO^lives"));
87 case "losses": return CTX(_("SCO^losses"));
88 case "name": return CTX(_("SCO^name"));
89 case "sum": return CTX(_("SCO^sum"));
90 case "nick": return CTX(_("SCO^nick"));
91 case "objectives": return CTX(_("SCO^objectives"));
92 case "pickups": return CTX(_("SCO^pickups"));
93 case "ping": return CTX(_("SCO^ping"));
94 case "pl": return CTX(_("SCO^pl"));
95 case "pushes": return CTX(_("SCO^pushes"));
96 case "rank": return CTX(_("SCO^rank"));
97 case "returns": return CTX(_("SCO^returns"));
98 case "revivals": return CTX(_("SCO^revivals"));
99 case "rounds": return CTX(_("SCO^rounds won"));
100 case "score": return CTX(_("SCO^score"));
101 case "suicides": return CTX(_("SCO^suicides"));
102 case "takes": return CTX(_("SCO^takes"));
103 case "ticks": return CTX(_("SCO^ticks"));
108 void Scoreboard_InitScores()
112 ps_primary = ps_secondary = NULL;
113 ts_primary = ts_secondary = -1;
114 FOREACH(Scores, true, {
115 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
116 if(f == SFL_SORT_PRIO_PRIMARY)
118 if(f == SFL_SORT_PRIO_SECONDARY)
121 if(ps_secondary == NULL)
122 ps_secondary = ps_primary;
124 for(i = 0; i < MAX_TEAMSCORE; ++i)
126 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
127 if(f == SFL_SORT_PRIO_PRIMARY)
129 if(f == SFL_SORT_PRIO_SECONDARY)
132 if(ts_secondary == -1)
133 ts_secondary = ts_primary;
135 Cmd_Scoreboard_SetFields(0);
138 float SetTeam(entity pl, float Team);
140 void Scoreboard_UpdatePlayerTeams()
147 for(pl = players.sort_next; pl; pl = pl.sort_next)
150 Team = entcs_GetScoreTeam(pl.sv_entnum);
151 if(SetTeam(pl, Team))
154 Scoreboard_UpdatePlayerPos(pl);
158 pl = players.sort_next;
163 print(strcat("PNUM: ", ftos(num), "\n"));
168 int Scoreboard_CompareScore(int vl, int vr, int f)
170 TC(int, vl); TC(int, vr); TC(int, f);
171 if(f & SFL_ZERO_IS_WORST)
173 if(vl == 0 && vr != 0)
175 if(vl != 0 && vr == 0)
179 return IS_INCREASING(f);
181 return IS_DECREASING(f);
185 float Scoreboard_ComparePlayerScores(entity left, entity right)
188 vl = entcs_GetTeam(left.sv_entnum);
189 vr = entcs_GetTeam(right.sv_entnum);
201 if(vl == NUM_SPECTATOR)
203 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
205 if(!left.gotscores && right.gotscores)
210 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
214 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
218 FOREACH(Scores, true, {
219 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
220 if (r >= 0) return r;
223 if (left.sv_entnum < right.sv_entnum)
229 void Scoreboard_UpdatePlayerPos(entity player)
232 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
234 SORT_SWAP(player, ent);
236 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
238 SORT_SWAP(ent, player);
242 float Scoreboard_CompareTeamScores(entity left, entity right)
246 if(left.team == NUM_SPECTATOR)
248 if(right.team == NUM_SPECTATOR)
251 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
255 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
259 for(i = 0; i < MAX_TEAMSCORE; ++i)
261 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
266 if (left.team < right.team)
272 void Scoreboard_UpdateTeamPos(entity Team)
275 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
277 SORT_SWAP(Team, ent);
279 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
281 SORT_SWAP(ent, Team);
285 void Cmd_Scoreboard_Help()
287 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
288 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
289 LOG_INFO(_("Usage:\n"));
290 LOG_INFO(_("^2scoreboard_columns_set default\n"));
291 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
292 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
293 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
296 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
297 LOG_INFO(_("^3ping^7 Ping time\n"));
298 LOG_INFO(_("^3pl^7 Packet loss\n"));
299 LOG_INFO(_("^3elo^7 Player ELO\n"));
300 LOG_INFO(_("^3kills^7 Number of kills\n"));
301 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
302 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
303 LOG_INFO(_("^3frags^7 kills - suicides\n"));
304 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
305 LOG_INFO(_("^3dmg^7 The total damage done\n"));
306 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
307 LOG_INFO(_("^3sum^7 frags - deaths\n"));
308 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
309 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
310 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
311 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
312 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
313 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
314 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
315 LOG_INFO(_("^3rank^7 Player rank\n"));
316 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
317 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
318 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
319 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
320 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
321 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
322 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
323 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
324 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
325 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
326 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
327 LOG_INFO(_("^3score^7 Total score\n"));
330 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
331 "of game types, then a slash, to make the field show up only in these\n"
332 "or in all but these game types. You can also specify 'all' as a\n"
333 "field to show all fields available for the current game mode.\n\n"));
335 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
336 "include/exclude ALL teams/noteams game modes.\n\n"));
338 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
339 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
340 "right of the vertical bar aligned to the right.\n"));
341 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
342 "other gamemodes except DM.\n"));
345 // NOTE: adding a gametype with ? to not warn for an optional field
346 // make sure it's excluded in a previous exclusive rule, if any
347 // otherwise the previous exclusive rule warns anyway
348 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
349 #define SCOREBOARD_DEFAULT_COLUMNS \
351 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
352 " -teams,lms/deaths +ft,tdm/deaths" \
353 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
354 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
355 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
356 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
357 " +lms/lives +lms/rank" \
358 " +kh/caps +kh/pushes +kh/destroyed" \
359 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
360 " +as/objectives +nb/faults +nb/goals" \
361 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
362 " -lms,rc,cts,inv,nb/score"
364 void Cmd_Scoreboard_SetFields(int argc)
369 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
373 return; // do nothing, we don't know gametype and scores yet
375 // sbt_fields uses strunzone on the titles!
376 if(!sbt_field_title[0])
377 for(i = 0; i < MAX_SBT_FIELDS; ++i)
378 sbt_field_title[i] = strzone("(null)");
380 // TODO: re enable with gametype dependant cvars?
381 if(argc < 3) // no arguments provided
382 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
385 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
389 if(argv(2) == "default")
390 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
391 else if(argv(2) == "all")
394 s = "ping pl name |";
395 FOREACH(Scores, true, {
397 if(it != ps_secondary)
398 if(scores_label(it) != "")
399 s = strcat(s, " ", scores_label(it));
401 if(ps_secondary != ps_primary)
402 s = strcat(s, " ", scores_label(ps_secondary));
403 s = strcat(s, " ", scores_label(ps_primary));
404 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
411 hud_fontsize = HUD_GetFontsize("hud_fontsize");
413 for(i = 1; i < argc - 1; ++i)
419 if(substring(str, 0, 1) == "?")
422 str = substring(str, 1, strlen(str) - 1);
425 slash = strstrofs(str, "/", 0);
428 pattern = substring(str, 0, slash);
429 str = substring(str, slash + 1, strlen(str) - (slash + 1));
431 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
435 strunzone(sbt_field_title[sbt_num_fields]);
436 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
437 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
438 str = strtolower(str);
443 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
444 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
445 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
446 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
447 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
448 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
449 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
450 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
451 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
454 FOREACH(Scores, true, {
455 if (str == strtolower(scores_label(it))) {
457 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
467 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
471 sbt_field[sbt_num_fields] = j;
474 if(j == ps_secondary)
480 if(sbt_num_fields >= MAX_SBT_FIELDS)
484 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
486 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
488 if(ps_primary == ps_secondary)
490 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
492 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
496 strunzone(sbt_field_title[sbt_num_fields]);
497 for(i = sbt_num_fields; i > 0; --i)
499 sbt_field_title[i] = sbt_field_title[i-1];
500 sbt_field_size[i] = sbt_field_size[i-1];
501 sbt_field[i] = sbt_field[i-1];
503 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
504 sbt_field[0] = SP_NAME;
506 LOG_INFO("fixed missing field 'name'\n");
510 strunzone(sbt_field_title[sbt_num_fields]);
511 for(i = sbt_num_fields; i > 1; --i)
513 sbt_field_title[i] = sbt_field_title[i-1];
514 sbt_field_size[i] = sbt_field_size[i-1];
515 sbt_field[i] = sbt_field[i-1];
517 sbt_field_title[1] = strzone("|");
518 sbt_field[1] = SP_SEPARATOR;
519 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
521 LOG_INFO("fixed missing field '|'\n");
524 else if(!have_separator)
526 strunzone(sbt_field_title[sbt_num_fields]);
527 sbt_field_title[sbt_num_fields] = strzone("|");
528 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
529 sbt_field[sbt_num_fields] = SP_SEPARATOR;
531 LOG_INFO("fixed missing field '|'\n");
535 strunzone(sbt_field_title[sbt_num_fields]);
536 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
537 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
538 sbt_field[sbt_num_fields] = ps_secondary;
540 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
544 strunzone(sbt_field_title[sbt_num_fields]);
545 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
546 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
547 sbt_field[sbt_num_fields] = ps_primary;
549 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
553 sbt_field[sbt_num_fields] = SP_END;
557 vector sbt_field_rgb;
558 string sbt_field_icon0;
559 string sbt_field_icon1;
560 string sbt_field_icon2;
561 vector sbt_field_icon0_rgb;
562 vector sbt_field_icon1_rgb;
563 vector sbt_field_icon2_rgb;
564 string Scoreboard_GetField(entity pl, PlayerScoreField field)
566 float tmp, num, denom;
569 sbt_field_rgb = '1 1 1';
570 sbt_field_icon0 = "";
571 sbt_field_icon1 = "";
572 sbt_field_icon2 = "";
573 sbt_field_icon0_rgb = '1 1 1';
574 sbt_field_icon1_rgb = '1 1 1';
575 sbt_field_icon2_rgb = '1 1 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);
663 case SP_DMG: case SP_DMGTAKEN:
664 return sprintf("%.1f k", pl.(scores(field)) / 1000);
667 tmp = pl.(scores(field));
668 f = scores_flags(field);
669 if(field == ps_primary)
670 sbt_field_rgb = '1 1 0';
671 else if(field == ps_secondary)
672 sbt_field_rgb = '0 1 1';
674 sbt_field_rgb = '1 1 1';
675 return ScoreString(f, tmp);
680 float sbt_fixcolumnwidth_len;
681 float sbt_fixcolumnwidth_iconlen;
682 float sbt_fixcolumnwidth_marginlen;
684 string Scoreboard_FixColumnWidth(int i, string str)
690 sbt_fixcolumnwidth_iconlen = 0;
692 if(sbt_field_icon0 != "")
694 sz = draw_getimagesize(sbt_field_icon0);
696 if(sbt_fixcolumnwidth_iconlen < f)
697 sbt_fixcolumnwidth_iconlen = f;
700 if(sbt_field_icon1 != "")
702 sz = draw_getimagesize(sbt_field_icon1);
704 if(sbt_fixcolumnwidth_iconlen < f)
705 sbt_fixcolumnwidth_iconlen = f;
708 if(sbt_field_icon2 != "")
710 sz = draw_getimagesize(sbt_field_icon2);
712 if(sbt_fixcolumnwidth_iconlen < f)
713 sbt_fixcolumnwidth_iconlen = f;
716 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
718 if(sbt_fixcolumnwidth_iconlen != 0)
719 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
721 sbt_fixcolumnwidth_marginlen = 0;
723 if(sbt_field[i] == SP_NAME) // name gets all remaining space
727 namesize = panel_size.x;
728 for(j = 0; j < sbt_num_fields; ++j)
730 if (sbt_field[i] != SP_SEPARATOR)
731 namesize -= sbt_field_size[j] + hud_fontsize.x;
732 sbt_field_size[i] = namesize;
734 if (sbt_fixcolumnwidth_iconlen != 0)
735 namesize -= sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
736 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
737 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
740 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
742 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
743 if(sbt_field_size[i] < f)
744 sbt_field_size[i] = f;
749 void Scoreboard_initFieldSizes()
751 for(int i = 0; i < sbt_num_fields; ++i)
752 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
755 vector Scoreboard_DrawHeader(vector pos, vector rgb)
758 vector column_dim = eY * panel_size.y;
759 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
760 pos.x += hud_fontsize.x * 0.5;
761 for(i = 0; i < sbt_num_fields; ++i)
763 if(sbt_field[i] == SP_SEPARATOR)
765 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
768 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
769 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
770 pos.x += column_dim.x;
772 if(sbt_field[i] == SP_SEPARATOR)
774 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
775 for(i = sbt_num_fields - 1; i > 0; --i)
777 if(sbt_field[i] == SP_SEPARATOR)
780 pos.x -= sbt_field_size[i];
785 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
786 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
789 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
790 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
791 pos.x -= hud_fontsize.x;
796 pos.y += 1.25 * hud_fontsize.y;
800 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
802 TC(bool, is_self); TC(int, pl_number);
804 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
806 vector h_pos = item_pos;
807 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
808 // alternated rows highlighting
810 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
811 else if((sbt_highlight) && (!(pl_number % 2)))
812 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
814 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
816 vector pos = item_pos;
817 pos.x += hud_fontsize.x * 0.5;
818 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
819 vector tmp = '0 0 0';
821 PlayerScoreField field;
822 for(i = 0; i < sbt_num_fields; ++i)
824 field = sbt_field[i];
825 if(field == SP_SEPARATOR)
828 if(is_spec && field != SP_NAME && field != SP_PING) {
829 pos.x += sbt_field_size[i] + hud_fontsize.x;
832 str = Scoreboard_GetField(pl, field);
833 str = Scoreboard_FixColumnWidth(i, str);
835 pos.x += sbt_field_size[i] + hud_fontsize.x;
837 if(field == SP_NAME) {
838 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
839 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
841 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
842 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
845 tmp.x = sbt_field_size[i] + hud_fontsize.x;
846 if(sbt_field_icon0 != "")
847 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);
848 if(sbt_field_icon1 != "")
849 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);
850 if(sbt_field_icon2 != "")
851 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);
854 if(sbt_field[i] == SP_SEPARATOR)
856 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
857 for(i = sbt_num_fields-1; i > 0; --i)
859 field = sbt_field[i];
860 if(field == SP_SEPARATOR)
863 if(is_spec && field != SP_NAME && field != SP_PING) {
864 pos.x -= sbt_field_size[i] + hud_fontsize.x;
868 str = Scoreboard_GetField(pl, field);
869 str = Scoreboard_FixColumnWidth(i, str);
871 if(field == SP_NAME) {
872 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
873 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
875 tmp.x = sbt_fixcolumnwidth_len;
876 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
879 tmp.x = sbt_field_size[i];
880 if(sbt_field_icon0 != "")
881 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);
882 if(sbt_field_icon1 != "")
883 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);
884 if(sbt_field_icon2 != "")
885 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);
886 pos.x -= sbt_field_size[i] + hud_fontsize.x;
891 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
894 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
899 panel_size.y = 1.25 * hud_fontsize.y * (1 + max(1, tm.team_size));
900 panel_size.y += panel_bg_padding * 2;
903 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
904 if(panel.current_panel_bg != "0")
905 end_pos.y += panel_bg_border * 2;
909 panel_pos += '1 1 0' * panel_bg_padding;
910 panel_size -= '2 2 0' * panel_bg_padding;
914 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
918 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
920 pos.y += 1.25 * hud_fontsize.y;
923 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
925 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
928 // print header row and highlight columns
929 pos = Scoreboard_DrawHeader(panel_pos, rgb);
931 // fill the table and draw the rows
934 for(pl = players.sort_next; pl; pl = pl.sort_next)
936 if(pl.team != tm.team)
938 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
939 pos.y += 1.25 * hud_fontsize.y;
943 for(pl = players.sort_next; pl; pl = pl.sort_next)
945 if(pl.team == NUM_SPECTATOR)
947 Scoreboard_DrawItem(pos, rgb, pl, (pl.sv_entnum == player_localnum), i);
948 pos.y += 1.25 * hud_fontsize.y;
952 panel_size.x += panel_bg_padding * 2; // restore initial width
956 float Scoreboard_WouldDraw() {
957 if (QuickMenu_IsOpened())
959 else if (HUD_Radar_Clickable())
961 else if (scoreboard_showscores)
963 else if (intermission == 1)
965 else if (intermission == 2)
967 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
969 else if (scoreboard_showscores_force)
974 float average_accuracy;
975 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
977 WepSet weapons_stat = WepSet_GetFromStat();
978 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
981 FOREACH(Weapons, it != WEP_Null, {
982 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
984 WepSet set = it.m_wepset;
985 if (weapon_stats < 0)
987 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
989 else if (!(weapons_stat & set || weapons_inmap & set))
994 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
995 if (weapon_cnt <= 0) return pos;
998 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1000 int columnns = ceil(weapon_cnt / rows);
1002 float weapon_height = 29;
1003 float height = hud_fontsize.y + weapon_height;
1005 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1006 pos.y += 1.25 * hud_fontsize.y;
1007 if(panel.current_panel_bg != "0")
1008 pos.y += panel_bg_border;
1011 panel_size.y = height * rows;
1012 panel_size.y += panel_bg_padding * 2;
1015 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1016 if(panel.current_panel_bg != "0")
1017 end_pos.y += panel_bg_border * 2;
1019 if(panel_bg_padding)
1021 panel_pos += '1 1 0' * panel_bg_padding;
1022 panel_size -= '2 2 0' * panel_bg_padding;
1026 vector tmp = panel_size;
1028 float weapon_width = tmp.x / columnns / rows;
1031 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1035 // column highlighting
1036 for (int i = 0; i < columnns; ++i)
1038 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1041 for (int i = 0; i < rows; ++i)
1042 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1045 average_accuracy = 0;
1046 int weapons_with_stats = 0;
1048 pos.x += weapon_width / 2;
1050 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1053 Accuracy_LoadColors();
1055 float oldposx = pos.x;
1059 FOREACH(Weapons, it != WEP_Null, {
1060 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1062 WepSet set = it.m_wepset;
1063 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1067 if (weapon_stats >= 0)
1068 weapon_alpha = sbt_fg_alpha;
1070 weapon_alpha = 0.2 * sbt_fg_alpha;
1073 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1075 if (weapon_stats >= 0) {
1076 weapons_with_stats += 1;
1077 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1080 s = sprintf("%d%%", weapon_stats * 100);
1083 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1085 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1086 rgb = Accuracy_GetColor(weapon_stats);
1088 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1090 tmpos.x += weapon_width * rows;
1091 pos.x += weapon_width * rows;
1092 if (rows == 2 && column == columnns - 1) {
1100 if (weapons_with_stats)
1101 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1103 panel_size.x += panel_bg_padding * 2; // restore initial width
1107 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1109 pos.x += hud_fontsize.x * 0.25;
1110 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1111 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1112 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1114 pos.y += hud_fontsize.y;
1119 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1120 float stat_secrets_found, stat_secrets_total;
1121 float stat_monsters_killed, stat_monsters_total;
1125 // get monster stats
1126 stat_monsters_killed = STAT(MONSTERS_KILLED);
1127 stat_monsters_total = STAT(MONSTERS_TOTAL);
1129 // get secrets stats
1130 stat_secrets_found = STAT(SECRETS_FOUND);
1131 stat_secrets_total = STAT(SECRETS_TOTAL);
1133 // get number of rows
1134 if(stat_secrets_total)
1136 if(stat_monsters_total)
1139 // if no rows, return
1143 // draw table header
1144 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1145 pos.y += 1.25 * hud_fontsize.y;
1146 if(panel.current_panel_bg != "0")
1147 pos.y += panel_bg_border;
1150 panel_size.y = hud_fontsize.y * rows;
1151 panel_size.y += panel_bg_padding * 2;
1154 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1155 if(panel.current_panel_bg != "0")
1156 end_pos.y += panel_bg_border * 2;
1158 if(panel_bg_padding)
1160 panel_pos += '1 1 0' * panel_bg_padding;
1161 panel_size -= '2 2 0' * panel_bg_padding;
1165 vector tmp = panel_size;
1168 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1171 if(stat_monsters_total)
1173 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1174 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1178 if(stat_secrets_total)
1180 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1181 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1184 panel_size.x += panel_bg_padding * 2; // restore initial width
1189 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1192 RANKINGS_RECEIVED_CNT = 0;
1193 for (i=RANKINGS_CNT-1; i>=0; --i)
1195 ++RANKINGS_RECEIVED_CNT;
1197 if (RANKINGS_RECEIVED_CNT == 0)
1200 vector hl_rgb = rgb + '0.5 0.5 0.5';
1202 pos.y += hud_fontsize.y;
1203 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1204 pos.y += 1.25 * hud_fontsize.y;
1205 if(panel.current_panel_bg != "0")
1206 pos.y += panel_bg_border;
1209 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1210 panel_size.y += panel_bg_padding * 2;
1213 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1214 if(panel.current_panel_bg != "0")
1215 end_pos.y += panel_bg_border * 2;
1217 if(panel_bg_padding)
1219 panel_pos += '1 1 0' * panel_bg_padding;
1220 panel_size -= '2 2 0' * panel_bg_padding;
1224 vector tmp = panel_size;
1227 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1230 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1237 n = grecordholder[i];
1238 p = count_ordinal(i+1);
1239 if(grecordholder[i] == entcs_GetName(player_localnum))
1240 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1241 else if(!(i % 2) && sbt_highlight)
1242 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1243 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1244 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);
1245 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1246 pos.y += 1.25 * hud_fontsize.y;
1249 panel_size.x += panel_bg_padding * 2; // restore initial width
1253 void Scoreboard_Draw()
1255 if(!autocvar__hud_configure)
1257 if(!hud_draw_maximized) return;
1259 // frametime checks allow to toggle the scoreboard even when the game is paused
1260 if(scoreboard_active) {
1261 if(hud_configure_menu_open == 1)
1262 scoreboard_fade_alpha = 1;
1263 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1264 if (scoreboard_fadeinspeed && frametime)
1265 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1267 scoreboard_fade_alpha = 1;
1268 if(hud_fontsize_str != autocvar_hud_fontsize)
1270 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1271 Scoreboard_initFieldSizes();
1272 if(hud_fontsize_str)
1273 strunzone(hud_fontsize_str);
1274 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1278 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1279 if (scoreboard_fadeoutspeed && frametime)
1280 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1282 scoreboard_fade_alpha = 0;
1285 if (!scoreboard_fade_alpha)
1289 scoreboard_fade_alpha = 0;
1291 if (autocvar_hud_panel_scoreboard_dynamichud)
1294 HUD_Scale_Disable();
1296 if(scoreboard_fade_alpha <= 0)
1298 panel_fade_alpha *= scoreboard_fade_alpha;
1299 HUD_Panel_LoadCvars();
1301 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1302 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1303 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1304 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1305 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1306 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1308 // don't overlap with con_notify
1309 if(!autocvar__hud_configure)
1310 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1312 Scoreboard_UpdatePlayerTeams();
1318 // Initializes position
1322 vector sb_heading_fontsize;
1323 sb_heading_fontsize = hud_fontsize * 2;
1324 draw_beginBoldFont();
1325 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1328 pos.y += sb_heading_fontsize.y;
1329 if(panel.current_panel_bg != "0")
1330 pos.y += panel_bg_border;
1332 // Draw the scoreboard
1333 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1336 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1340 vector panel_bg_color_save = panel_bg_color;
1341 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1342 if(panel.current_panel_bg != "0")
1343 team_score_baseoffset.x -= panel_bg_border;
1344 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1346 if(tm.team == NUM_SPECTATOR)
1351 draw_beginBoldFont();
1352 vector rgb = Team_ColorRGB(tm.team);
1353 str = ftos(tm.(teamscores(ts_primary)));
1354 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1356 if(ts_primary != ts_secondary)
1358 str = ftos(tm.(teamscores(ts_secondary)));
1359 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);
1362 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1363 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1364 else if(panel_bg_color_team > 0)
1365 panel_bg_color = rgb * panel_bg_color_team;
1367 panel_bg_color = rgb;
1368 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1370 panel_bg_color = panel_bg_color_save;
1374 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1376 if(tm.team == NUM_SPECTATOR)
1379 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1383 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1384 if(race_speedaward) {
1385 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);
1386 pos.y += 1.25 * hud_fontsize.y;
1388 if(race_speedaward_alltimebest) {
1389 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);
1390 pos.y += 1.25 * hud_fontsize.y;
1392 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1394 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1395 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1397 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1402 for(pl = players.sort_next; pl; pl = pl.sort_next)
1404 if(pl.team != NUM_SPECTATOR)
1406 pos.y += 1.25 * hud_fontsize.y;
1407 Scoreboard_DrawItem(pos, '0 0 0', pl, (pl.sv_entnum == player_localnum), specs);
1413 draw_beginBoldFont();
1414 drawstring(tmp, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1416 pos.y += 1.25 * hud_fontsize.y;
1419 // Print info string
1421 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1422 tl = STAT(TIMELIMIT);
1423 fl = STAT(FRAGLIMIT);
1424 ll = STAT(LEADLIMIT);
1425 if(gametype == MAPINFO_TYPE_LMS)
1428 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1433 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1437 str = strcat(str, _(" or"));
1440 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1441 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1442 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1443 TranslateScoresLabel(teamscores_label(ts_primary))));
1447 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1448 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1449 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1450 TranslateScoresLabel(scores_label(ps_primary))));
1455 if(tl > 0 || fl > 0)
1456 str = strcat(str, _(" or"));
1459 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1460 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1461 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1462 TranslateScoresLabel(teamscores_label(ts_primary))));
1466 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1467 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1468 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1469 TranslateScoresLabel(scores_label(ps_primary))));
1474 pos.y += 1.2 * hud_fontsize.y;
1475 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1477 // print information about respawn status
1478 float respawn_time = STAT(RESPAWN_TIME);
1482 if(respawn_time < 0)
1484 // a negative number means we are awaiting respawn, time value is still the same
1485 respawn_time *= -1; // remove mark now that we checked it
1487 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1488 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1490 str = sprintf(_("^1Respawning in ^3%s^1..."),
1491 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1492 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1494 count_seconds(ceil(respawn_time - time))
1498 else if(time < respawn_time)
1500 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1501 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1502 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1504 count_seconds(ceil(respawn_time - time))
1508 else if(time >= respawn_time)
1509 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1511 pos.y += 1.2 * hud_fontsize.y;
1512 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1515 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;