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;
26 float sbt_fg_alpha_self;
28 float sbt_highlight_alpha;
29 float sbt_highlight_alpha_self;
31 // provide basic panel cvars to old clients
32 // TODO remove them after a future release (0.8.2+)
33 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
34 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
35 string autocvar_hud_panel_scoreboard_bg = "border_default";
36 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
37 string autocvar_hud_panel_scoreboard_bg_color_team = "";
38 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
39 string autocvar_hud_panel_scoreboard_bg_border = "";
40 string autocvar_hud_panel_scoreboard_bg_padding = "";
42 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
43 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
44 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
45 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
46 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
49 bool autocvar_hud_panel_scoreboard_table_highlight = true;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
52 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 float autocvar_hud_panel_scoreboard_namesize = 15;
55 bool autocvar_hud_panel_scoreboard_accuracy = true;
56 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
57 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_dynamichud = false;
61 bool autocvar_hud_panel_scoreboard_maxheight = 0.5;
62 bool autocvar_hud_panel_scoreboard_others_showscore = true;
63 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
66 void drawstringright(vector, string, vector, vector, float, float);
67 void drawstringcenter(vector, string, vector, vector, float, float);
69 // wrapper to put all possible scores titles through gettext
70 string TranslateScoresLabel(string l)
74 case "bckills": return CTX(_("SCO^bckills"));
75 case "bctime": return CTX(_("SCO^bctime"));
76 case "caps": return CTX(_("SCO^caps"));
77 case "captime": return CTX(_("SCO^captime"));
78 case "deaths": return CTX(_("SCO^deaths"));
79 case "destroyed": return CTX(_("SCO^destroyed"));
80 case "dmg": return CTX(_("SCO^damage"));
81 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
82 case "drops": return CTX(_("SCO^drops"));
83 case "faults": return CTX(_("SCO^faults"));
84 case "fckills": return CTX(_("SCO^fckills"));
85 case "goals": return CTX(_("SCO^goals"));
86 case "kckills": return CTX(_("SCO^kckills"));
87 case "kdratio": return CTX(_("SCO^kdratio"));
88 case "kd": return CTX(_("SCO^k/d"));
89 case "kdr": return CTX(_("SCO^kdr"));
90 case "kills": return CTX(_("SCO^kills"));
91 case "laps": return CTX(_("SCO^laps"));
92 case "lives": return CTX(_("SCO^lives"));
93 case "losses": return CTX(_("SCO^losses"));
94 case "name": return CTX(_("SCO^name"));
95 case "sum": return CTX(_("SCO^sum"));
96 case "nick": return CTX(_("SCO^nick"));
97 case "objectives": return CTX(_("SCO^objectives"));
98 case "pickups": return CTX(_("SCO^pickups"));
99 case "ping": return CTX(_("SCO^ping"));
100 case "pl": return CTX(_("SCO^pl"));
101 case "pushes": return CTX(_("SCO^pushes"));
102 case "rank": return CTX(_("SCO^rank"));
103 case "returns": return CTX(_("SCO^returns"));
104 case "revivals": return CTX(_("SCO^revivals"));
105 case "rounds": return CTX(_("SCO^rounds won"));
106 case "score": return CTX(_("SCO^score"));
107 case "suicides": return CTX(_("SCO^suicides"));
108 case "takes": return CTX(_("SCO^takes"));
109 case "ticks": return CTX(_("SCO^ticks"));
114 void Scoreboard_InitScores()
118 ps_primary = ps_secondary = NULL;
119 ts_primary = ts_secondary = -1;
120 FOREACH(Scores, true, {
121 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
122 if(f == SFL_SORT_PRIO_PRIMARY)
124 if(f == SFL_SORT_PRIO_SECONDARY)
127 if(ps_secondary == NULL)
128 ps_secondary = ps_primary;
130 for(i = 0; i < MAX_TEAMSCORE; ++i)
132 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
133 if(f == SFL_SORT_PRIO_PRIMARY)
135 if(f == SFL_SORT_PRIO_SECONDARY)
138 if(ts_secondary == -1)
139 ts_secondary = ts_primary;
141 Cmd_Scoreboard_SetFields(0);
144 float SetTeam(entity pl, float Team);
146 void Scoreboard_UpdatePlayerTeams()
153 for(pl = players.sort_next; pl; pl = pl.sort_next)
156 Team = entcs_GetScoreTeam(pl.sv_entnum);
157 if(SetTeam(pl, Team))
160 Scoreboard_UpdatePlayerPos(pl);
164 pl = players.sort_next;
169 print(strcat("PNUM: ", ftos(num), "\n"));
174 int Scoreboard_CompareScore(int vl, int vr, int f)
176 TC(int, vl); TC(int, vr); TC(int, f);
177 if(f & SFL_ZERO_IS_WORST)
179 if(vl == 0 && vr != 0)
181 if(vl != 0 && vr == 0)
185 return IS_INCREASING(f);
187 return IS_DECREASING(f);
191 float Scoreboard_ComparePlayerScores(entity left, entity right)
194 vl = entcs_GetTeam(left.sv_entnum);
195 vr = entcs_GetTeam(right.sv_entnum);
207 if(vl == NUM_SPECTATOR)
209 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
211 if(!left.gotscores && right.gotscores)
216 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
220 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
224 FOREACH(Scores, true, {
225 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
226 if (r >= 0) return r;
229 if (left.sv_entnum < right.sv_entnum)
235 void Scoreboard_UpdatePlayerPos(entity player)
238 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
240 SORT_SWAP(player, ent);
242 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
244 SORT_SWAP(ent, player);
248 float Scoreboard_CompareTeamScores(entity left, entity right)
252 if(left.team == NUM_SPECTATOR)
254 if(right.team == NUM_SPECTATOR)
257 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
261 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
265 for(i = 0; i < MAX_TEAMSCORE; ++i)
267 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
272 if (left.team < right.team)
278 void Scoreboard_UpdateTeamPos(entity Team)
281 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
283 SORT_SWAP(Team, ent);
285 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
287 SORT_SWAP(ent, Team);
291 void Cmd_Scoreboard_Help()
293 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
294 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
295 LOG_INFO(_("Usage:\n"));
296 LOG_INFO(_("^2scoreboard_columns_set default\n"));
297 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
298 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
299 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
302 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
303 LOG_INFO(_("^3ping^7 Ping time\n"));
304 LOG_INFO(_("^3pl^7 Packet loss\n"));
305 LOG_INFO(_("^3elo^7 Player ELO\n"));
306 LOG_INFO(_("^3kills^7 Number of kills\n"));
307 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
308 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
309 LOG_INFO(_("^3frags^7 kills - suicides\n"));
310 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
311 LOG_INFO(_("^3dmg^7 The total damage done\n"));
312 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
313 LOG_INFO(_("^3sum^7 frags - deaths\n"));
314 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
315 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
316 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
317 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
318 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
319 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
320 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
321 LOG_INFO(_("^3rank^7 Player rank\n"));
322 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
323 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
324 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
325 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
326 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
327 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
328 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
329 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
330 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
331 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
332 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
333 LOG_INFO(_("^3score^7 Total score\n"));
336 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
337 "of game types, then a slash, to make the field show up only in these\n"
338 "or in all but these game types. You can also specify 'all' as a\n"
339 "field to show all fields available for the current game mode.\n\n"));
341 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
342 "include/exclude ALL teams/noteams game modes.\n\n"));
344 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
345 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
346 "right of the vertical bar aligned to the right.\n"));
347 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
348 "other gamemodes except DM.\n"));
351 // NOTE: adding a gametype with ? to not warn for an optional field
352 // make sure it's excluded in a previous exclusive rule, if any
353 // otherwise the previous exclusive rule warns anyway
354 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
355 #define SCOREBOARD_DEFAULT_COLUMNS \
357 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
358 " -teams,lms/deaths +ft,tdm/deaths" \
359 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
360 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
361 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
362 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
363 " +lms/lives +lms/rank" \
364 " +kh/caps +kh/pushes +kh/destroyed" \
365 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
366 " +as/objectives +nb/faults +nb/goals" \
367 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
368 " -lms,rc,cts,inv,nb/score"
370 void Cmd_Scoreboard_SetFields(int argc)
375 float have_name = 0, have_primary = 0, have_secondary = 0, have_separator = 0;
379 return; // do nothing, we don't know gametype and scores yet
381 // sbt_fields uses strunzone on the titles!
382 if(!sbt_field_title[0])
383 for(i = 0; i < MAX_SBT_FIELDS; ++i)
384 sbt_field_title[i] = strzone("(null)");
386 // TODO: re enable with gametype dependant cvars?
387 if(argc < 3) // no arguments provided
388 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
391 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
395 if(argv(2) == "default")
396 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 else if(argv(2) == "all")
400 s = "ping pl name |";
401 FOREACH(Scores, true, {
403 if(it != ps_secondary)
404 if(scores_label(it) != "")
405 s = strcat(s, " ", scores_label(it));
407 if(ps_secondary != ps_primary)
408 s = strcat(s, " ", scores_label(ps_secondary));
409 s = strcat(s, " ", scores_label(ps_primary));
410 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
417 hud_fontsize = HUD_GetFontsize("hud_fontsize");
419 for(i = 1; i < argc - 1; ++i)
425 if(substring(str, 0, 1) == "?")
428 str = substring(str, 1, strlen(str) - 1);
431 slash = strstrofs(str, "/", 0);
434 pattern = substring(str, 0, slash);
435 str = substring(str, slash + 1, strlen(str) - (slash + 1));
437 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
441 strunzone(sbt_field_title[sbt_num_fields]);
442 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
443 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
444 str = strtolower(str);
449 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
450 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
451 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
452 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
453 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
454 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
455 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
456 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
457 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
460 FOREACH(Scores, true, {
461 if (str == strtolower(scores_label(it))) {
463 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
473 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
477 sbt_field[sbt_num_fields] = j;
480 if(j == ps_secondary)
486 if(sbt_num_fields >= MAX_SBT_FIELDS)
490 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
492 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
494 if(ps_primary == ps_secondary)
496 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
498 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
502 strunzone(sbt_field_title[sbt_num_fields]);
503 for(i = sbt_num_fields; i > 0; --i)
505 sbt_field_title[i] = sbt_field_title[i-1];
506 sbt_field_size[i] = sbt_field_size[i-1];
507 sbt_field[i] = sbt_field[i-1];
509 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
510 sbt_field[0] = SP_NAME;
512 LOG_INFO("fixed missing field 'name'\n");
516 strunzone(sbt_field_title[sbt_num_fields]);
517 for(i = sbt_num_fields; i > 1; --i)
519 sbt_field_title[i] = sbt_field_title[i-1];
520 sbt_field_size[i] = sbt_field_size[i-1];
521 sbt_field[i] = sbt_field[i-1];
523 sbt_field_title[1] = strzone("|");
524 sbt_field[1] = SP_SEPARATOR;
525 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
527 LOG_INFO("fixed missing field '|'\n");
530 else if(!have_separator)
532 strunzone(sbt_field_title[sbt_num_fields]);
533 sbt_field_title[sbt_num_fields] = strzone("|");
534 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
535 sbt_field[sbt_num_fields] = SP_SEPARATOR;
537 LOG_INFO("fixed missing field '|'\n");
541 strunzone(sbt_field_title[sbt_num_fields]);
542 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
543 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
544 sbt_field[sbt_num_fields] = ps_secondary;
546 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
550 strunzone(sbt_field_title[sbt_num_fields]);
551 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
552 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
553 sbt_field[sbt_num_fields] = ps_primary;
555 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
559 sbt_field[sbt_num_fields] = SP_END;
563 vector sbt_field_rgb;
564 string sbt_field_icon0;
565 string sbt_field_icon1;
566 string sbt_field_icon2;
567 vector sbt_field_icon0_rgb;
568 vector sbt_field_icon1_rgb;
569 vector sbt_field_icon2_rgb;
570 string Scoreboard_GetField(entity pl, PlayerScoreField field)
572 float tmp, num, denom;
575 sbt_field_rgb = '1 1 1';
576 sbt_field_icon0 = "";
577 sbt_field_icon1 = "";
578 sbt_field_icon2 = "";
579 sbt_field_icon0_rgb = '1 1 1';
580 sbt_field_icon1_rgb = '1 1 1';
581 sbt_field_icon2_rgb = '1 1 1';
586 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
587 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
591 tmp = max(0, min(220, f-80)) / 220;
592 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
598 f = pl.ping_packetloss;
599 tmp = pl.ping_movementloss;
600 if(f == 0 && tmp == 0)
602 str = ftos(ceil(f * 100));
604 str = strcat(str, "~", ftos(ceil(tmp * 100)));
605 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
606 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
610 if(ready_waiting && pl.ready)
612 sbt_field_icon0 = "gfx/scoreboard/player_ready";
616 f = entcs_GetClientColors(pl.sv_entnum);
618 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
619 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
620 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
621 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
622 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
625 return entcs_GetName(pl.sv_entnum);
628 f = pl.(scores(SP_KILLS));
629 f -= pl.(scores(SP_SUICIDES));
633 num = pl.(scores(SP_KILLS));
634 denom = pl.(scores(SP_DEATHS));
637 sbt_field_rgb = '0 1 0';
638 str = sprintf("%d", num);
639 } else if(num <= 0) {
640 sbt_field_rgb = '1 0 0';
641 str = sprintf("%.1f", num/denom);
643 str = sprintf("%.1f", num/denom);
647 f = pl.(scores(SP_KILLS));
648 f -= pl.(scores(SP_DEATHS));
651 sbt_field_rgb = '0 1 0';
653 sbt_field_rgb = '1 1 1';
655 sbt_field_rgb = '1 0 0';
661 float elo = pl.(scores(SP_ELO));
663 case -1: return "...";
664 case -2: return _("N/A");
665 default: return ftos(elo);
669 case SP_DMG: case SP_DMGTAKEN:
670 return sprintf("%.1f k", pl.(scores(field)) / 1000);
673 tmp = pl.(scores(field));
674 f = scores_flags(field);
675 if(field == ps_primary)
676 sbt_field_rgb = '1 1 0';
677 else if(field == ps_secondary)
678 sbt_field_rgb = '0 1 1';
680 sbt_field_rgb = '1 1 1';
681 return ScoreString(f, tmp);
686 float sbt_fixcolumnwidth_len;
687 float sbt_fixcolumnwidth_iconlen;
688 float sbt_fixcolumnwidth_marginlen;
690 string Scoreboard_FixColumnWidth(int i, string str)
696 sbt_fixcolumnwidth_iconlen = 0;
698 if(sbt_field_icon0 != "")
700 sz = draw_getimagesize(sbt_field_icon0);
702 if(sbt_fixcolumnwidth_iconlen < f)
703 sbt_fixcolumnwidth_iconlen = f;
706 if(sbt_field_icon1 != "")
708 sz = draw_getimagesize(sbt_field_icon1);
710 if(sbt_fixcolumnwidth_iconlen < f)
711 sbt_fixcolumnwidth_iconlen = f;
714 if(sbt_field_icon2 != "")
716 sz = draw_getimagesize(sbt_field_icon2);
718 if(sbt_fixcolumnwidth_iconlen < f)
719 sbt_fixcolumnwidth_iconlen = f;
722 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
724 if(sbt_fixcolumnwidth_iconlen != 0)
725 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
727 sbt_fixcolumnwidth_marginlen = 0;
729 if(sbt_field[i] == SP_NAME) // name gets all remaining space
732 float remaining_space = 0;
733 for(j = 0; j < sbt_num_fields; ++j)
735 if (sbt_field[i] != SP_SEPARATOR)
736 remaining_space += sbt_field_size[j] + hud_fontsize.x;
737 sbt_field_size[i] = panel_size.x - remaining_space;
739 if (sbt_fixcolumnwidth_iconlen != 0)
740 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
741 float namesize = panel_size.x - remaining_space;
742 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
743 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
745 max_namesize = vid_conwidth - remaining_space;
748 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
750 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 if(sbt_field_size[i] < f)
752 sbt_field_size[i] = f;
757 void Scoreboard_initFieldSizes()
759 for(int i = 0; i < sbt_num_fields; ++i)
760 Scoreboard_FixColumnWidth(i, "");
763 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
766 vector column_dim = eY * panel_size.y;
768 column_dim.y -= 1.25 * hud_fontsize.y;
769 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
770 pos.x += hud_fontsize.x * 0.5;
771 for(i = 0; i < sbt_num_fields; ++i)
773 if(sbt_field[i] == SP_SEPARATOR)
775 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
778 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
779 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
780 pos.x += column_dim.x;
782 if(sbt_field[i] == SP_SEPARATOR)
784 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
785 for(i = sbt_num_fields - 1; i > 0; --i)
787 if(sbt_field[i] == SP_SEPARATOR)
790 pos.x -= sbt_field_size[i];
795 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
796 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
799 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
800 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
801 pos.x -= hud_fontsize.x;
806 pos.y += 1.25 * hud_fontsize.y;
810 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
812 TC(bool, is_self); TC(int, pl_number);
814 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
816 vector h_pos = item_pos;
817 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
818 // alternated rows highlighting
820 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
821 else if((sbt_highlight) && (!(pl_number % 2)))
822 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
824 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
826 vector pos = item_pos;
827 pos.x += hud_fontsize.x * 0.5;
828 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
829 vector tmp = '0 0 0';
831 PlayerScoreField field;
832 for(i = 0; i < sbt_num_fields; ++i)
834 field = sbt_field[i];
835 if(field == SP_SEPARATOR)
838 if(is_spec && field != SP_NAME && field != SP_PING) {
839 pos.x += sbt_field_size[i] + hud_fontsize.x;
842 str = Scoreboard_GetField(pl, field);
843 str = Scoreboard_FixColumnWidth(i, str);
845 pos.x += sbt_field_size[i] + hud_fontsize.x;
847 if(field == SP_NAME) {
848 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
849 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
851 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
852 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
855 tmp.x = sbt_field_size[i] + hud_fontsize.x;
856 if(sbt_field_icon0 != "")
857 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);
858 if(sbt_field_icon1 != "")
859 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);
860 if(sbt_field_icon2 != "")
861 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);
864 if(sbt_field[i] == SP_SEPARATOR)
866 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
867 for(i = sbt_num_fields-1; i > 0; --i)
869 field = sbt_field[i];
870 if(field == SP_SEPARATOR)
873 if(is_spec && field != SP_NAME && field != SP_PING) {
874 pos.x -= sbt_field_size[i] + hud_fontsize.x;
878 str = Scoreboard_GetField(pl, field);
879 str = Scoreboard_FixColumnWidth(i, str);
881 if(field == SP_NAME) {
882 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
883 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
885 tmp.x = sbt_fixcolumnwidth_len;
886 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
889 tmp.x = sbt_field_size[i];
890 if(sbt_field_icon0 != "")
891 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);
892 if(sbt_field_icon1 != "")
893 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);
894 if(sbt_field_icon2 != "")
895 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);
896 pos.x -= sbt_field_size[i] + hud_fontsize.x;
901 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
904 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
907 vector h_pos = item_pos;
908 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
910 bool complete = (this_team == NUM_SPECTATOR);
913 if((sbt_highlight) && (!(pl_number % 2)))
914 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
916 vector pos = item_pos;
917 pos.x += hud_fontsize.x * 0.5;
918 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
920 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
922 width_limit -= stringwidth("...", false, hud_fontsize);
923 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
924 float ping_padding = 0;
925 float min_pingsize = stringwidth("999", false, hud_fontsize);
926 for(i = 0; pl; pl = pl.sort_next)
928 if(pl.team != this_team)
934 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
935 if(this_team == NUM_SPECTATOR)
937 if(autocvar_hud_panel_scoreboard_spectators_showping)
939 string ping = Scoreboard_GetField(pl, SP_PING);
940 float pingsize = stringwidth(ping, false, hud_fontsize);
941 if(min_pingsize > pingsize)
942 ping_padding = min_pingsize - pingsize;
943 string col = rgb_to_hexcolor(sbt_field_rgb);
944 str = sprintf("%s ^7[%s%s^7]", str, col, ping);
947 else if(autocvar_hud_panel_scoreboard_others_showscore)
948 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
949 float str_width = stringwidth(str, true, hud_fontsize);
950 if(pos.x + str_width > width_limit)
955 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
960 pos.x = item_pos.x + hud_fontsize.x * 0.5;
961 pos.y = item_pos.y + i * (hud_fontsize.y * 1.25);
964 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
965 pos.x += str_width + hud_fontsize.x * 0.5;
966 pos.x += ping_padding;
968 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
971 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
973 int max_players = 999;
974 if(autocvar_hud_panel_scoreboard_maxheight > 0)
976 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
978 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
979 max_players = floor(max_players / (hud_fontsize.y * 1.25));
982 if(max_players == tm.team_size)
987 entity me = playerslots[current_player];
989 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
990 panel_size.y += panel_bg_padding * 2;
993 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
994 if(panel.current_panel_bg != "0")
995 end_pos.y += panel_bg_border * 2;
999 panel_pos += '1 1 0' * panel_bg_padding;
1000 panel_size -= '2 2 0' * panel_bg_padding;
1004 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1008 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1010 pos.y += 1.25 * hud_fontsize.y;
1013 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1015 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1018 // print header row and highlight columns
1019 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1021 // fill the table and draw the rows
1022 bool is_self = false;
1023 bool self_shown = false;
1025 for(pl = players.sort_next; pl; pl = pl.sort_next)
1027 if(pl.team != tm.team)
1029 if(i == max_players - 2 && pl != me)
1031 if(!self_shown && me.team == tm.team)
1033 Scoreboard_DrawItem(pos, rgb, me, true, i);
1035 pos.y += 1.25 * hud_fontsize.y;
1039 if(i >= max_players - 1)
1041 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1044 is_self = (pl.sv_entnum == current_player);
1045 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1048 pos.y += 1.25 * hud_fontsize.y;
1052 panel_size.x += panel_bg_padding * 2; // restore initial width
1056 float Scoreboard_WouldDraw() {
1057 if (QuickMenu_IsOpened())
1059 else if (HUD_Radar_Clickable())
1061 else if (scoreboard_showscores)
1063 else if (intermission == 1)
1065 else if (intermission == 2)
1067 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1069 else if (scoreboard_showscores_force)
1074 float average_accuracy;
1075 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1077 WepSet weapons_stat = WepSet_GetFromStat();
1078 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1079 int disownedcnt = 0;
1081 FOREACH(Weapons, it != WEP_Null, {
1082 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1084 WepSet set = it.m_wepset;
1085 if (weapon_stats < 0)
1087 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1089 else if (!(weapons_stat & set || weapons_inmap & set))
1094 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1095 if (weapon_cnt <= 0) return pos;
1098 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1100 int columnns = ceil(weapon_cnt / rows);
1102 float weapon_height = 29;
1103 float height = hud_fontsize.y + weapon_height;
1105 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1106 pos.y += 1.25 * hud_fontsize.y;
1107 if(panel.current_panel_bg != "0")
1108 pos.y += panel_bg_border;
1111 panel_size.y = height * rows;
1112 panel_size.y += panel_bg_padding * 2;
1115 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1116 if(panel.current_panel_bg != "0")
1117 end_pos.y += panel_bg_border * 2;
1119 if(panel_bg_padding)
1121 panel_pos += '1 1 0' * panel_bg_padding;
1122 panel_size -= '2 2 0' * panel_bg_padding;
1126 vector tmp = panel_size;
1128 float weapon_width = tmp.x / columnns / rows;
1131 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1135 // column highlighting
1136 for (int i = 0; i < columnns; ++i)
1138 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1141 for (int i = 0; i < rows; ++i)
1142 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1145 average_accuracy = 0;
1146 int weapons_with_stats = 0;
1148 pos.x += weapon_width / 2;
1150 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1153 Accuracy_LoadColors();
1155 float oldposx = pos.x;
1159 FOREACH(Weapons, it != WEP_Null, {
1160 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1162 WepSet set = it.m_wepset;
1163 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1167 if (weapon_stats >= 0)
1168 weapon_alpha = sbt_fg_alpha;
1170 weapon_alpha = 0.2 * sbt_fg_alpha;
1173 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1175 if (weapon_stats >= 0) {
1176 weapons_with_stats += 1;
1177 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1180 s = sprintf("%d%%", weapon_stats * 100);
1183 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1185 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1186 rgb = Accuracy_GetColor(weapon_stats);
1188 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1190 tmpos.x += weapon_width * rows;
1191 pos.x += weapon_width * rows;
1192 if (rows == 2 && column == columnns - 1) {
1200 if (weapons_with_stats)
1201 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1203 panel_size.x += panel_bg_padding * 2; // restore initial width
1207 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1209 pos.x += hud_fontsize.x * 0.25;
1210 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1211 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1212 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1214 pos.y += hud_fontsize.y;
1219 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1220 float stat_secrets_found, stat_secrets_total;
1221 float stat_monsters_killed, stat_monsters_total;
1225 // get monster stats
1226 stat_monsters_killed = STAT(MONSTERS_KILLED);
1227 stat_monsters_total = STAT(MONSTERS_TOTAL);
1229 // get secrets stats
1230 stat_secrets_found = STAT(SECRETS_FOUND);
1231 stat_secrets_total = STAT(SECRETS_TOTAL);
1233 // get number of rows
1234 if(stat_secrets_total)
1236 if(stat_monsters_total)
1239 // if no rows, return
1243 // draw table header
1244 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1245 pos.y += 1.25 * hud_fontsize.y;
1246 if(panel.current_panel_bg != "0")
1247 pos.y += panel_bg_border;
1250 panel_size.y = hud_fontsize.y * rows;
1251 panel_size.y += panel_bg_padding * 2;
1254 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1255 if(panel.current_panel_bg != "0")
1256 end_pos.y += panel_bg_border * 2;
1258 if(panel_bg_padding)
1260 panel_pos += '1 1 0' * panel_bg_padding;
1261 panel_size -= '2 2 0' * panel_bg_padding;
1265 vector tmp = panel_size;
1268 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1271 if(stat_monsters_total)
1273 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1274 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1278 if(stat_secrets_total)
1280 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1281 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1284 panel_size.x += panel_bg_padding * 2; // restore initial width
1289 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1292 RANKINGS_RECEIVED_CNT = 0;
1293 for (i=RANKINGS_CNT-1; i>=0; --i)
1295 ++RANKINGS_RECEIVED_CNT;
1297 if (RANKINGS_RECEIVED_CNT == 0)
1300 vector hl_rgb = rgb + '0.5 0.5 0.5';
1302 pos.y += hud_fontsize.y;
1303 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1304 pos.y += 1.25 * hud_fontsize.y;
1305 if(panel.current_panel_bg != "0")
1306 pos.y += panel_bg_border;
1309 panel_size.y = 1.25 * hud_fontsize.y * RANKINGS_RECEIVED_CNT;
1310 panel_size.y += panel_bg_padding * 2;
1313 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1314 if(panel.current_panel_bg != "0")
1315 end_pos.y += panel_bg_border * 2;
1317 if(panel_bg_padding)
1319 panel_pos += '1 1 0' * panel_bg_padding;
1320 panel_size -= '2 2 0' * panel_bg_padding;
1324 vector tmp = panel_size;
1327 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1330 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1337 n = grecordholder[i];
1338 p = count_ordinal(i+1);
1339 if(grecordholder[i] == entcs_GetName(player_localnum))
1340 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1341 else if(!(i % 2) && sbt_highlight)
1342 drawfill(pos, eX * panel_size.x + '0 1.25 0' * hud_fontsize.y, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1343 drawstring(pos, p, '1 1 0' * hud_fontsize.y, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1344 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);
1345 drawcolorcodedstring(pos + '8 0 0' * hud_fontsize.y, n, '1 1 0' * hud_fontsize.y, sbt_fg_alpha, DRAWFLAG_NORMAL);
1346 pos.y += 1.25 * hud_fontsize.y;
1349 panel_size.x += panel_bg_padding * 2; // restore initial width
1353 void Scoreboard_Draw()
1355 if(!autocvar__hud_configure)
1357 if(!hud_draw_maximized) return;
1359 // frametime checks allow to toggle the scoreboard even when the game is paused
1360 if(scoreboard_active) {
1361 if(hud_configure_menu_open == 1)
1362 scoreboard_fade_alpha = 1;
1363 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1364 if (scoreboard_fadeinspeed && frametime)
1365 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1367 scoreboard_fade_alpha = 1;
1368 if(hud_fontsize_str != autocvar_hud_fontsize)
1370 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1371 Scoreboard_initFieldSizes();
1372 if(hud_fontsize_str)
1373 strunzone(hud_fontsize_str);
1374 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1378 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1379 if (scoreboard_fadeoutspeed && frametime)
1380 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1382 scoreboard_fade_alpha = 0;
1385 if (!scoreboard_fade_alpha)
1389 scoreboard_fade_alpha = 0;
1391 if (autocvar_hud_panel_scoreboard_dynamichud)
1394 HUD_Scale_Disable();
1396 if(scoreboard_fade_alpha <= 0)
1398 panel_fade_alpha *= scoreboard_fade_alpha;
1399 HUD_Panel_LoadCvars();
1401 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1402 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1403 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1404 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1405 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1406 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1408 // don't overlap with con_notify
1409 if(!autocvar__hud_configure)
1410 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1412 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1413 float fixed_scoreboard_width = bound(vid_conwidth * 0.4, vid_conwidth - excess, vid_conwidth * 0.93);
1414 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1415 panel_size.x = fixed_scoreboard_width;
1417 Scoreboard_UpdatePlayerTeams();
1419 vector pos = panel_pos;
1424 vector sb_heading_fontsize;
1425 sb_heading_fontsize = hud_fontsize * 2;
1426 draw_beginBoldFont();
1427 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1430 pos.y += sb_heading_fontsize.y;
1431 if(panel.current_panel_bg != "0")
1432 pos.y += panel_bg_border;
1434 // Draw the scoreboard
1435 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1438 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1442 vector panel_bg_color_save = panel_bg_color;
1443 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1444 if(panel.current_panel_bg != "0")
1445 team_score_baseoffset.x -= panel_bg_border;
1446 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1448 if(tm.team == NUM_SPECTATOR)
1453 draw_beginBoldFont();
1454 vector rgb = Team_ColorRGB(tm.team);
1455 str = ftos(tm.(teamscores(ts_primary)));
1456 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1458 if(ts_primary != ts_secondary)
1460 str = ftos(tm.(teamscores(ts_secondary)));
1461 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);
1464 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1465 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1466 else if(panel_bg_color_team > 0)
1467 panel_bg_color = rgb * panel_bg_color_team;
1469 panel_bg_color = rgb;
1470 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1472 panel_bg_color = panel_bg_color_save;
1476 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1477 if(tm.team != NUM_SPECTATOR)
1479 // display it anyway
1480 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1483 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1484 if(race_speedaward) {
1485 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);
1486 pos.y += 1.25 * hud_fontsize.y;
1488 if(race_speedaward_alltimebest) {
1489 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);
1490 pos.y += 1.25 * hud_fontsize.y;
1492 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1494 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1495 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1497 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1500 for(pl = players.sort_next; pl; pl = pl.sort_next)
1502 if(pl.team == NUM_SPECTATOR)
1504 draw_beginBoldFont();
1505 drawstring(pos, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1507 pos.y += 1.25 * hud_fontsize.y;
1509 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1510 pos.y += 1.25 * hud_fontsize.y;
1516 // Print info string
1518 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1519 tl = STAT(TIMELIMIT);
1520 fl = STAT(FRAGLIMIT);
1521 ll = STAT(LEADLIMIT);
1522 if(gametype == MAPINFO_TYPE_LMS)
1525 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1530 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1534 str = strcat(str, _(" or"));
1537 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1538 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1539 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1540 TranslateScoresLabel(teamscores_label(ts_primary))));
1544 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1545 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1546 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1547 TranslateScoresLabel(scores_label(ps_primary))));
1552 if(tl > 0 || fl > 0)
1553 str = strcat(str, _(" or"));
1556 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1557 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1558 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1559 TranslateScoresLabel(teamscores_label(ts_primary))));
1563 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1564 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1565 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1566 TranslateScoresLabel(scores_label(ps_primary))));
1571 pos.y += 1.2 * hud_fontsize.y;
1572 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1574 // print information about respawn status
1575 float respawn_time = STAT(RESPAWN_TIME);
1579 if(respawn_time < 0)
1581 // a negative number means we are awaiting respawn, time value is still the same
1582 respawn_time *= -1; // remove mark now that we checked it
1584 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1585 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1587 str = sprintf(_("^1Respawning in ^3%s^1..."),
1588 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1589 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1591 count_seconds(ceil(respawn_time - time))
1595 else if(time < respawn_time)
1597 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1598 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1599 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1601 count_seconds(ceil(respawn_time - time))
1605 else if(time >= respawn_time)
1606 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1608 pos.y += 1.2 * hud_fontsize.y;
1609 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1612 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;