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 float 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;
64 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
67 void drawstringright(vector, string, vector, vector, float, float);
68 void drawstringcenter(vector, string, vector, vector, float, float);
70 // wrapper to put all possible scores titles through gettext
71 string TranslateScoresLabel(string l)
75 case "bckills": return CTX(_("SCO^bckills"));
76 case "bctime": return CTX(_("SCO^bctime"));
77 case "caps": return CTX(_("SCO^caps"));
78 case "captime": return CTX(_("SCO^captime"));
79 case "deaths": return CTX(_("SCO^deaths"));
80 case "destroyed": return CTX(_("SCO^destroyed"));
81 case "dmg": return CTX(_("SCO^damage"));
82 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
83 case "drops": return CTX(_("SCO^drops"));
84 case "faults": return CTX(_("SCO^faults"));
85 case "fckills": return CTX(_("SCO^fckills"));
86 case "goals": return CTX(_("SCO^goals"));
87 case "kckills": return CTX(_("SCO^kckills"));
88 case "kdratio": return CTX(_("SCO^kdratio"));
89 case "kd": return CTX(_("SCO^k/d"));
90 case "kdr": return CTX(_("SCO^kdr"));
91 case "kills": return CTX(_("SCO^kills"));
92 case "laps": return CTX(_("SCO^laps"));
93 case "lives": return CTX(_("SCO^lives"));
94 case "losses": return CTX(_("SCO^losses"));
95 case "name": return CTX(_("SCO^name"));
96 case "sum": return CTX(_("SCO^sum"));
97 case "nick": return CTX(_("SCO^nick"));
98 case "objectives": return CTX(_("SCO^objectives"));
99 case "pickups": return CTX(_("SCO^pickups"));
100 case "ping": return CTX(_("SCO^ping"));
101 case "pl": return CTX(_("SCO^pl"));
102 case "pushes": return CTX(_("SCO^pushes"));
103 case "rank": return CTX(_("SCO^rank"));
104 case "returns": return CTX(_("SCO^returns"));
105 case "revivals": return CTX(_("SCO^revivals"));
106 case "rounds": return CTX(_("SCO^rounds won"));
107 case "score": return CTX(_("SCO^score"));
108 case "suicides": return CTX(_("SCO^suicides"));
109 case "takes": return CTX(_("SCO^takes"));
110 case "ticks": return CTX(_("SCO^ticks"));
115 void Scoreboard_InitScores()
119 ps_primary = ps_secondary = NULL;
120 ts_primary = ts_secondary = -1;
121 FOREACH(Scores, true, {
122 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
123 if(f == SFL_SORT_PRIO_PRIMARY)
125 if(f == SFL_SORT_PRIO_SECONDARY)
128 if(ps_secondary == NULL)
129 ps_secondary = ps_primary;
131 for(i = 0; i < MAX_TEAMSCORE; ++i)
133 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
134 if(f == SFL_SORT_PRIO_PRIMARY)
136 if(f == SFL_SORT_PRIO_SECONDARY)
139 if(ts_secondary == -1)
140 ts_secondary = ts_primary;
142 Cmd_Scoreboard_SetFields(0);
145 float SetTeam(entity pl, float Team);
147 void Scoreboard_UpdatePlayerTeams()
152 for(pl = players.sort_next; pl; pl = pl.sort_next)
155 Team = entcs_GetScoreTeam(pl.sv_entnum);
156 if(SetTeam(pl, Team))
159 Scoreboard_UpdatePlayerPos(pl);
163 pl = players.sort_next;
168 print(strcat("PNUM: ", ftos(num), "\n"));
173 int Scoreboard_CompareScore(int vl, int vr, int f)
175 TC(int, vl); TC(int, vr); TC(int, f);
176 if(f & SFL_ZERO_IS_WORST)
178 if(vl == 0 && vr != 0)
180 if(vl != 0 && vr == 0)
184 return IS_INCREASING(f);
186 return IS_DECREASING(f);
190 float Scoreboard_ComparePlayerScores(entity left, entity right)
193 vl = entcs_GetTeam(left.sv_entnum);
194 vr = entcs_GetTeam(right.sv_entnum);
206 if(vl == NUM_SPECTATOR)
208 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
210 if(!left.gotscores && right.gotscores)
215 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
219 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
223 FOREACH(Scores, true, {
224 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
225 if (r >= 0) return r;
228 if (left.sv_entnum < right.sv_entnum)
234 void Scoreboard_UpdatePlayerPos(entity player)
237 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
239 SORT_SWAP(player, ent);
241 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
243 SORT_SWAP(ent, player);
247 float Scoreboard_CompareTeamScores(entity left, entity right)
251 if(left.team == NUM_SPECTATOR)
253 if(right.team == NUM_SPECTATOR)
256 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
260 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
264 for(i = 0; i < MAX_TEAMSCORE; ++i)
266 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
271 if (left.team < right.team)
277 void Scoreboard_UpdateTeamPos(entity Team)
280 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
282 SORT_SWAP(Team, ent);
284 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
286 SORT_SWAP(ent, Team);
290 void Cmd_Scoreboard_Help()
292 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
293 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
294 LOG_INFO(_("Usage:\n"));
295 LOG_INFO(_("^2scoreboard_columns_set default\n"));
296 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
297 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
298 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
301 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
302 LOG_INFO(_("^3ping^7 Ping time\n"));
303 LOG_INFO(_("^3pl^7 Packet loss\n"));
304 LOG_INFO(_("^3elo^7 Player ELO\n"));
305 LOG_INFO(_("^3kills^7 Number of kills\n"));
306 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
307 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
308 LOG_INFO(_("^3frags^7 kills - suicides\n"));
309 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
310 LOG_INFO(_("^3dmg^7 The total damage done\n"));
311 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
312 LOG_INFO(_("^3sum^7 frags - deaths\n"));
313 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
314 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
315 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
316 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
317 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
318 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
319 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
320 LOG_INFO(_("^3rank^7 Player rank\n"));
321 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
322 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
323 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
324 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
325 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
326 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
327 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
328 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
329 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
330 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
331 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
332 LOG_INFO(_("^3score^7 Total score\n"));
335 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
336 "of game types, then a slash, to make the field show up only in these\n"
337 "or in all but these game types. You can also specify 'all' as a\n"
338 "field to show all fields available for the current game mode.\n\n"));
340 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
341 "include/exclude ALL teams/noteams game modes.\n\n"));
343 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
344 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
345 "right of the vertical bar aligned to the right.\n"));
346 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
347 "other gamemodes except DM.\n"));
350 // NOTE: adding a gametype with ? to not warn for an optional field
351 // make sure it's excluded in a previous exclusive rule, if any
352 // otherwise the previous exclusive rule warns anyway
353 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
354 #define SCOREBOARD_DEFAULT_COLUMNS \
356 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
357 " -teams,lms/deaths +ft,tdm/deaths" \
358 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
359 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
360 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
361 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
362 " +lms/lives +lms/rank" \
363 " +kh/caps +kh/pushes +kh/destroyed" \
364 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
365 " +as/objectives +nb/faults +nb/goals" \
366 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
367 " -lms,rc,cts,inv,nb/score"
369 void Cmd_Scoreboard_SetFields(int argc)
374 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
378 return; // do nothing, we don't know gametype and scores yet
380 // sbt_fields uses strunzone on the titles!
381 if(!sbt_field_title[0])
382 for(i = 0; i < MAX_SBT_FIELDS; ++i)
383 sbt_field_title[i] = strzone("(null)");
385 // TODO: re enable with gametype dependant cvars?
386 if(argc < 3) // no arguments provided
387 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
390 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
394 if(argv(2) == "default")
395 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
396 else if(argv(2) == "all")
399 s = "ping pl name |";
400 FOREACH(Scores, true, {
402 if(it != ps_secondary)
403 if(scores_label(it) != "")
404 s = strcat(s, " ", scores_label(it));
406 if(ps_secondary != ps_primary)
407 s = strcat(s, " ", scores_label(ps_secondary));
408 s = strcat(s, " ", scores_label(ps_primary));
409 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
416 hud_fontsize = HUD_GetFontsize("hud_fontsize");
418 for(i = 1; i < argc - 1; ++i)
424 if(substring(str, 0, 1) == "?")
427 str = substring(str, 1, strlen(str) - 1);
430 slash = strstrofs(str, "/", 0);
433 pattern = substring(str, 0, slash);
434 str = substring(str, slash + 1, strlen(str) - (slash + 1));
436 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
440 strunzone(sbt_field_title[sbt_num_fields]);
441 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
442 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
443 str = strtolower(str);
448 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
449 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
450 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
451 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
452 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
453 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
454 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
455 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
456 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
459 FOREACH(Scores, true, {
460 if (str == strtolower(scores_label(it))) {
462 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
472 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
476 sbt_field[sbt_num_fields] = j;
479 if(j == ps_secondary)
480 have_secondary = true;
485 if(sbt_num_fields >= MAX_SBT_FIELDS)
489 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
491 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
492 have_secondary = true;
493 if(ps_primary == ps_secondary)
494 have_secondary = true;
495 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
497 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
501 strunzone(sbt_field_title[sbt_num_fields]);
502 for(i = sbt_num_fields; i > 0; --i)
504 sbt_field_title[i] = sbt_field_title[i-1];
505 sbt_field_size[i] = sbt_field_size[i-1];
506 sbt_field[i] = sbt_field[i-1];
508 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
509 sbt_field[0] = SP_NAME;
511 LOG_INFO("fixed missing field 'name'\n");
515 strunzone(sbt_field_title[sbt_num_fields]);
516 for(i = sbt_num_fields; i > 1; --i)
518 sbt_field_title[i] = sbt_field_title[i-1];
519 sbt_field_size[i] = sbt_field_size[i-1];
520 sbt_field[i] = sbt_field[i-1];
522 sbt_field_title[1] = strzone("|");
523 sbt_field[1] = SP_SEPARATOR;
524 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
526 LOG_INFO("fixed missing field '|'\n");
529 else if(!have_separator)
531 strunzone(sbt_field_title[sbt_num_fields]);
532 sbt_field_title[sbt_num_fields] = strzone("|");
533 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
534 sbt_field[sbt_num_fields] = SP_SEPARATOR;
536 LOG_INFO("fixed missing field '|'\n");
540 strunzone(sbt_field_title[sbt_num_fields]);
541 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
542 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
543 sbt_field[sbt_num_fields] = ps_secondary;
545 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
549 strunzone(sbt_field_title[sbt_num_fields]);
550 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
551 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
552 sbt_field[sbt_num_fields] = ps_primary;
554 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
558 sbt_field[sbt_num_fields] = SP_END;
562 vector sbt_field_rgb;
563 string sbt_field_icon0;
564 string sbt_field_icon1;
565 string sbt_field_icon2;
566 vector sbt_field_icon0_rgb;
567 vector sbt_field_icon1_rgb;
568 vector sbt_field_icon2_rgb;
569 string Scoreboard_GetField(entity pl, PlayerScoreField field)
571 float tmp, num, denom;
574 sbt_field_rgb = '1 1 1';
575 sbt_field_icon0 = "";
576 sbt_field_icon1 = "";
577 sbt_field_icon2 = "";
578 sbt_field_icon0_rgb = '1 1 1';
579 sbt_field_icon1_rgb = '1 1 1';
580 sbt_field_icon2_rgb = '1 1 1';
585 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
586 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
590 tmp = max(0, min(220, f-80)) / 220;
591 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
597 f = pl.ping_packetloss;
598 tmp = pl.ping_movementloss;
599 if(f == 0 && tmp == 0)
601 str = ftos(ceil(f * 100));
603 str = strcat(str, "~", ftos(ceil(tmp * 100)));
604 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
605 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
609 if(ready_waiting && pl.ready)
611 sbt_field_icon0 = "gfx/scoreboard/player_ready";
615 f = entcs_GetClientColors(pl.sv_entnum);
617 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
618 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
619 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
620 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
621 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
624 return entcs_GetName(pl.sv_entnum);
627 f = pl.(scores(SP_KILLS));
628 f -= pl.(scores(SP_SUICIDES));
632 num = pl.(scores(SP_KILLS));
633 denom = pl.(scores(SP_DEATHS));
636 sbt_field_rgb = '0 1 0';
637 str = sprintf("%d", num);
638 } else if(num <= 0) {
639 sbt_field_rgb = '1 0 0';
640 str = sprintf("%.1f", num/denom);
642 str = sprintf("%.1f", num/denom);
646 f = pl.(scores(SP_KILLS));
647 f -= pl.(scores(SP_DEATHS));
650 sbt_field_rgb = '0 1 0';
652 sbt_field_rgb = '1 1 1';
654 sbt_field_rgb = '1 0 0';
660 float elo = pl.(scores(SP_ELO));
662 case -1: return "...";
663 case -2: return _("N/A");
664 default: return ftos(elo);
668 case SP_DMG: case SP_DMGTAKEN:
669 return sprintf("%.1f k", pl.(scores(field)) / 1000);
672 tmp = pl.(scores(field));
673 f = scores_flags(field);
674 if(field == ps_primary)
675 sbt_field_rgb = '1 1 0';
676 else if(field == ps_secondary)
677 sbt_field_rgb = '0 1 1';
679 sbt_field_rgb = '1 1 1';
680 return ScoreString(f, tmp);
685 float sbt_fixcolumnwidth_len;
686 float sbt_fixcolumnwidth_iconlen;
687 float sbt_fixcolumnwidth_marginlen;
689 string Scoreboard_FixColumnWidth(int i, string str)
695 sbt_fixcolumnwidth_iconlen = 0;
697 if(sbt_field_icon0 != "")
699 sz = draw_getimagesize(sbt_field_icon0);
701 if(sbt_fixcolumnwidth_iconlen < f)
702 sbt_fixcolumnwidth_iconlen = f;
705 if(sbt_field_icon1 != "")
707 sz = draw_getimagesize(sbt_field_icon1);
709 if(sbt_fixcolumnwidth_iconlen < f)
710 sbt_fixcolumnwidth_iconlen = f;
713 if(sbt_field_icon2 != "")
715 sz = draw_getimagesize(sbt_field_icon2);
717 if(sbt_fixcolumnwidth_iconlen < f)
718 sbt_fixcolumnwidth_iconlen = f;
721 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
723 if(sbt_fixcolumnwidth_iconlen != 0)
724 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
726 sbt_fixcolumnwidth_marginlen = 0;
728 if(sbt_field[i] == SP_NAME) // name gets all remaining space
731 float remaining_space = 0;
732 for(j = 0; j < sbt_num_fields; ++j)
734 if (sbt_field[i] != SP_SEPARATOR)
735 remaining_space += sbt_field_size[j] + hud_fontsize.x;
736 sbt_field_size[i] = panel_size.x - remaining_space;
738 if (sbt_fixcolumnwidth_iconlen != 0)
739 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
740 float namesize = panel_size.x - remaining_space;
741 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
742 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
744 max_namesize = vid_conwidth - remaining_space;
747 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
749 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
750 if(sbt_field_size[i] < f)
751 sbt_field_size[i] = f;
756 void Scoreboard_initFieldSizes()
758 for(int i = 0; i < sbt_num_fields; ++i)
759 Scoreboard_FixColumnWidth(i, "");
762 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
765 vector column_dim = eY * panel_size.y;
767 column_dim.y -= 1.25 * hud_fontsize.y;
768 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
769 pos.x += hud_fontsize.x * 0.5;
770 for(i = 0; i < sbt_num_fields; ++i)
772 if(sbt_field[i] == SP_SEPARATOR)
774 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
777 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
778 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
779 pos.x += column_dim.x;
781 if(sbt_field[i] == SP_SEPARATOR)
783 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
784 for(i = sbt_num_fields - 1; i > 0; --i)
786 if(sbt_field[i] == SP_SEPARATOR)
789 pos.x -= sbt_field_size[i];
794 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
795 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
798 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
799 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
800 pos.x -= hud_fontsize.x;
805 pos.y += 1.25 * hud_fontsize.y;
809 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
811 TC(bool, is_self); TC(int, pl_number);
813 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
815 vector h_pos = item_pos;
816 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
817 // alternated rows highlighting
819 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
820 else if((sbt_highlight) && (!(pl_number % 2)))
821 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
823 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
825 vector pos = item_pos;
826 pos.x += hud_fontsize.x * 0.5;
827 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
828 vector tmp = '0 0 0';
830 PlayerScoreField field;
831 for(i = 0; i < sbt_num_fields; ++i)
833 field = sbt_field[i];
834 if(field == SP_SEPARATOR)
837 if(is_spec && field != SP_NAME && field != SP_PING) {
838 pos.x += sbt_field_size[i] + hud_fontsize.x;
841 str = Scoreboard_GetField(pl, field);
842 str = Scoreboard_FixColumnWidth(i, str);
844 pos.x += sbt_field_size[i] + hud_fontsize.x;
846 if(field == SP_NAME) {
847 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
848 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
850 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
851 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
854 tmp.x = sbt_field_size[i] + hud_fontsize.x;
855 if(sbt_field_icon0 != "")
856 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);
857 if(sbt_field_icon1 != "")
858 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);
859 if(sbt_field_icon2 != "")
860 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);
863 if(sbt_field[i] == SP_SEPARATOR)
865 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
866 for(i = sbt_num_fields-1; i > 0; --i)
868 field = sbt_field[i];
869 if(field == SP_SEPARATOR)
872 if(is_spec && field != SP_NAME && field != SP_PING) {
873 pos.x -= sbt_field_size[i] + hud_fontsize.x;
877 str = Scoreboard_GetField(pl, field);
878 str = Scoreboard_FixColumnWidth(i, str);
880 if(field == SP_NAME) {
881 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
882 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
884 tmp.x = sbt_fixcolumnwidth_len;
885 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
888 tmp.x = sbt_field_size[i];
889 if(sbt_field_icon0 != "")
890 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);
891 if(sbt_field_icon1 != "")
892 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);
893 if(sbt_field_icon2 != "")
894 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);
895 pos.x -= sbt_field_size[i] + hud_fontsize.x;
900 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
903 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
906 vector h_pos = item_pos;
907 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
909 bool complete = (this_team == NUM_SPECTATOR);
912 if((sbt_highlight) && (!(pl_number % 2)))
913 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
915 vector pos = item_pos;
916 pos.x += hud_fontsize.x * 0.5;
917 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
919 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
921 width_limit -= stringwidth("...", false, hud_fontsize);
922 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
923 float ping_padding = 0;
924 float min_pingsize = stringwidth("999", false, hud_fontsize);
925 for(i = 0; pl; pl = pl.sort_next)
927 if(pl.team != this_team)
933 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
934 if(this_team == NUM_SPECTATOR)
936 if(autocvar_hud_panel_scoreboard_spectators_showping)
938 string ping = Scoreboard_GetField(pl, SP_PING);
939 float pingsize = stringwidth(ping, false, hud_fontsize);
940 if(min_pingsize > pingsize)
941 ping_padding = min_pingsize - pingsize;
942 string col = rgb_to_hexcolor(sbt_field_rgb);
943 str = sprintf("%s ^7[%s%s^7]", str, col, ping);
946 else if(autocvar_hud_panel_scoreboard_others_showscore)
947 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
948 float str_width = stringwidth(str, true, hud_fontsize);
949 if(pos.x + str_width > width_limit)
954 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
959 pos.x = item_pos.x + hud_fontsize.x * 0.5;
960 pos.y = item_pos.y + i * (hud_fontsize.y * 1.25);
963 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
964 pos.x += str_width + hud_fontsize.x * 0.5;
965 pos.x += ping_padding;
967 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
970 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
972 int max_players = 999;
973 if(autocvar_hud_panel_scoreboard_maxheight > 0)
975 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
977 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
978 max_players = floor(max_players / (hud_fontsize.y * 1.25));
981 if(max_players == tm.team_size)
986 entity me = playerslots[current_player];
988 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
989 panel_size.y += panel_bg_padding * 2;
992 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
993 if(panel.current_panel_bg != "0")
994 end_pos.y += panel_bg_border * 2;
998 panel_pos += '1 1 0' * panel_bg_padding;
999 panel_size -= '2 2 0' * panel_bg_padding;
1003 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1007 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1009 pos.y += 1.25 * hud_fontsize.y;
1012 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1014 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1017 // print header row and highlight columns
1018 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1020 // fill the table and draw the rows
1021 bool is_self = false;
1022 bool self_shown = false;
1024 for(pl = players.sort_next; pl; pl = pl.sort_next)
1026 if(pl.team != tm.team)
1028 if(i == max_players - 2 && pl != me)
1030 if(!self_shown && me.team == tm.team)
1032 Scoreboard_DrawItem(pos, rgb, me, true, i);
1034 pos.y += 1.25 * hud_fontsize.y;
1038 if(i >= max_players - 1)
1040 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1043 is_self = (pl.sv_entnum == current_player);
1044 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1047 pos.y += 1.25 * hud_fontsize.y;
1051 panel_size.x += panel_bg_padding * 2; // restore initial width
1055 bool 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;
1311 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1313 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1318 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1320 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1324 float ranksize = 3 * hud_fontsize.x;
1325 float timesize = 5.5 * hud_fontsize.x;
1326 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1327 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1328 columns = min(columns, RANKINGS_RECEIVED_CNT);
1330 // expand name column to fill the entire row
1331 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1332 namesize += available_space;
1333 columnsize.x += available_space;
1335 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1336 panel_size.y += panel_bg_padding * 2;
1340 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1341 if(panel.current_panel_bg != "0")
1342 end_pos.y += panel_bg_border * 2;
1344 if(panel_bg_padding)
1346 panel_pos += '1 1 0' * panel_bg_padding;
1347 panel_size -= '2 2 0' * panel_bg_padding;
1353 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1355 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1357 int column = 0, j = 0;
1358 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1365 if(grecordholder[i] == entcs_GetName(player_localnum))
1366 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1367 else if(!((j + column) & 1) && sbt_highlight)
1368 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1370 str = count_ordinal(i+1);
1371 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1372 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1373 str = grecordholder[i];
1375 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1376 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1378 pos.y += 1.25 * hud_fontsize.y;
1380 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1384 pos.x += panel_size.x / columns;
1385 pos.y = panel_pos.y;
1389 panel_size.x += panel_bg_padding * 2; // restore initial width
1393 void Scoreboard_Draw()
1395 if(!autocvar__hud_configure)
1397 if(!hud_draw_maximized) return;
1399 // frametime checks allow to toggle the scoreboard even when the game is paused
1400 if(scoreboard_active) {
1401 if(hud_configure_menu_open == 1)
1402 scoreboard_fade_alpha = 1;
1403 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1404 if (scoreboard_fadeinspeed && frametime)
1405 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1407 scoreboard_fade_alpha = 1;
1408 if(hud_fontsize_str != autocvar_hud_fontsize)
1410 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1411 Scoreboard_initFieldSizes();
1412 if(hud_fontsize_str)
1413 strunzone(hud_fontsize_str);
1414 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1418 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1419 if (scoreboard_fadeoutspeed && frametime)
1420 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1422 scoreboard_fade_alpha = 0;
1425 if (!scoreboard_fade_alpha)
1429 scoreboard_fade_alpha = 0;
1431 if (autocvar_hud_panel_scoreboard_dynamichud)
1434 HUD_Scale_Disable();
1436 if(scoreboard_fade_alpha <= 0)
1438 panel_fade_alpha *= scoreboard_fade_alpha;
1439 HUD_Panel_LoadCvars();
1441 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1442 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1443 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1444 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1445 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1446 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1448 // don't overlap with con_notify
1449 if(!autocvar__hud_configure)
1450 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1452 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1453 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1454 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1455 panel_size.x = fixed_scoreboard_width;
1457 Scoreboard_UpdatePlayerTeams();
1459 vector pos = panel_pos;
1464 vector sb_heading_fontsize;
1465 sb_heading_fontsize = hud_fontsize * 2;
1466 draw_beginBoldFont();
1467 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1470 pos.y += sb_heading_fontsize.y;
1471 if(panel.current_panel_bg != "0")
1472 pos.y += panel_bg_border;
1474 // Draw the scoreboard
1475 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1478 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1482 vector panel_bg_color_save = panel_bg_color;
1483 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1484 if(panel.current_panel_bg != "0")
1485 team_score_baseoffset.x -= panel_bg_border;
1486 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1488 if(tm.team == NUM_SPECTATOR)
1493 draw_beginBoldFont();
1494 vector rgb = Team_ColorRGB(tm.team);
1495 str = ftos(tm.(teamscores(ts_primary)));
1496 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1498 if(ts_primary != ts_secondary)
1500 str = ftos(tm.(teamscores(ts_secondary)));
1501 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);
1504 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1505 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1506 else if(panel_bg_color_team > 0)
1507 panel_bg_color = rgb * panel_bg_color_team;
1509 panel_bg_color = rgb;
1510 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1512 panel_bg_color = panel_bg_color_save;
1516 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1517 if(tm.team != NUM_SPECTATOR)
1519 // display it anyway
1520 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1523 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1524 if(race_speedaward) {
1525 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);
1526 pos.y += 1.25 * hud_fontsize.y;
1528 if(race_speedaward_alltimebest) {
1529 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);
1530 pos.y += 1.25 * hud_fontsize.y;
1532 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1534 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1535 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1537 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1540 for(pl = players.sort_next; pl; pl = pl.sort_next)
1542 if(pl.team == NUM_SPECTATOR)
1544 draw_beginBoldFont();
1545 drawstring(pos, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1547 pos.y += 1.25 * hud_fontsize.y;
1549 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1550 pos.y += 1.25 * hud_fontsize.y;
1556 // Print info string
1558 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1559 tl = STAT(TIMELIMIT);
1560 fl = STAT(FRAGLIMIT);
1561 ll = STAT(LEADLIMIT);
1562 if(gametype == MAPINFO_TYPE_LMS)
1565 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1570 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1574 str = strcat(str, _(" or"));
1577 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1578 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1579 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1580 TranslateScoresLabel(teamscores_label(ts_primary))));
1584 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1585 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1586 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1587 TranslateScoresLabel(scores_label(ps_primary))));
1592 if(tl > 0 || fl > 0)
1593 str = strcat(str, _(" or"));
1596 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1597 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1598 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1599 TranslateScoresLabel(teamscores_label(ts_primary))));
1603 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1604 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1605 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1606 TranslateScoresLabel(scores_label(ps_primary))));
1611 pos.y += 1.2 * hud_fontsize.y;
1612 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1614 // print information about respawn status
1615 float respawn_time = STAT(RESPAWN_TIME);
1619 if(respawn_time < 0)
1621 // a negative number means we are awaiting respawn, time value is still the same
1622 respawn_time *= -1; // remove mark now that we checked it
1624 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1625 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1627 str = sprintf(_("^1Respawning in ^3%s^1..."),
1628 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1629 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1631 count_seconds(ceil(respawn_time - time))
1635 else if(time < respawn_time)
1637 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1638 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1639 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1641 count_seconds(ceil(respawn_time - time))
1645 else if(time >= respawn_time)
1646 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1648 pos.y += 1.2 * hud_fontsize.y;
1649 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1652 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;