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)
760 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
761 Scoreboard_FixColumnWidth(i, "");
765 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
768 vector column_dim = eY * panel_size.y;
770 column_dim.y -= 1.25 * hud_fontsize.y;
771 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
772 pos.x += hud_fontsize.x * 0.5;
773 for(i = 0; i < sbt_num_fields; ++i)
775 if(sbt_field[i] == SP_SEPARATOR)
777 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
780 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
781 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
782 pos.x += column_dim.x;
784 if(sbt_field[i] == SP_SEPARATOR)
786 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
787 for(i = sbt_num_fields - 1; i > 0; --i)
789 if(sbt_field[i] == SP_SEPARATOR)
792 pos.x -= sbt_field_size[i];
797 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
798 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
801 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
802 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
803 pos.x -= hud_fontsize.x;
808 pos.y += 1.25 * hud_fontsize.y;
812 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
814 TC(bool, is_self); TC(int, pl_number);
816 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
818 vector h_pos = item_pos;
819 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
820 // alternated rows highlighting
822 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
823 else if((sbt_highlight) && (!(pl_number % 2)))
824 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
826 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
828 vector pos = item_pos;
829 pos.x += hud_fontsize.x * 0.5;
830 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
831 vector tmp = '0 0 0';
833 PlayerScoreField field;
834 for(i = 0; i < sbt_num_fields; ++i)
836 field = sbt_field[i];
837 if(field == SP_SEPARATOR)
840 if(is_spec && field != SP_NAME && field != SP_PING) {
841 pos.x += sbt_field_size[i] + hud_fontsize.x;
844 str = Scoreboard_GetField(pl, field);
845 str = Scoreboard_FixColumnWidth(i, str);
847 pos.x += sbt_field_size[i] + hud_fontsize.x;
849 if(field == SP_NAME) {
850 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
851 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
854 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
857 tmp.x = sbt_field_size[i] + hud_fontsize.x;
858 if(sbt_field_icon0 != "")
859 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);
860 if(sbt_field_icon1 != "")
861 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);
862 if(sbt_field_icon2 != "")
863 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);
866 if(sbt_field[i] == SP_SEPARATOR)
868 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
869 for(i = sbt_num_fields-1; i > 0; --i)
871 field = sbt_field[i];
872 if(field == SP_SEPARATOR)
875 if(is_spec && field != SP_NAME && field != SP_PING) {
876 pos.x -= sbt_field_size[i] + hud_fontsize.x;
880 str = Scoreboard_GetField(pl, field);
881 str = Scoreboard_FixColumnWidth(i, str);
883 if(field == SP_NAME) {
884 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
885 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_fixcolumnwidth_len;
888 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
891 tmp.x = sbt_field_size[i];
892 if(sbt_field_icon0 != "")
893 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);
894 if(sbt_field_icon1 != "")
895 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);
896 if(sbt_field_icon2 != "")
897 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);
898 pos.x -= sbt_field_size[i] + hud_fontsize.x;
903 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
906 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
909 vector h_pos = item_pos;
910 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
912 bool complete = (this_team == NUM_SPECTATOR);
915 if((sbt_highlight) && (!(pl_number % 2)))
916 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
918 vector pos = item_pos;
919 pos.x += hud_fontsize.x * 0.5;
920 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
922 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
924 width_limit -= stringwidth("...", false, hud_fontsize);
925 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
928 float min_fieldsize = 0;
929 float fieldpadding = hud_fontsize.x * 0.25;
930 if(this_team == NUM_SPECTATOR)
932 if(autocvar_hud_panel_scoreboard_spectators_showping)
933 min_fieldsize = stringwidth("999", false, hud_fontsize);
935 else if(autocvar_hud_panel_scoreboard_others_showscore)
936 min_fieldsize = stringwidth("99", false, hud_fontsize);
937 for(i = 0; pl; pl = pl.sort_next)
939 if(pl.team != this_team)
945 if(this_team == NUM_SPECTATOR)
947 if(autocvar_hud_panel_scoreboard_spectators_showping)
948 field = Scoreboard_GetField(pl, SP_PING);
950 else if(autocvar_hud_panel_scoreboard_others_showscore)
951 field = ftos(pl.(scores(ps_primary)));
953 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
954 float str_width = stringwidth(str, true, hud_fontsize);
957 fieldsize = stringwidth(field, false, hud_fontsize);
958 str_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
961 if(pos.x + str_width > width_limit)
966 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
971 pos.x = item_pos.x + hud_fontsize.x * 0.5;
972 pos.y += hud_fontsize.y * 1.25;
975 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
979 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
980 h_size.y = hud_fontsize.y;
983 drawfill(pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
984 drawstring(pos + eX * (fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5), field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
987 pos.x += hud_fontsize.x;
989 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
992 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
994 int max_players = 999;
995 if(autocvar_hud_panel_scoreboard_maxheight > 0)
997 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
999 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
1000 max_players = floor(max_players / (hud_fontsize.y * 1.25));
1001 if(max_players <= 1)
1003 if(max_players == tm.team_size)
1008 entity me = playerslots[current_player];
1010 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1011 panel_size.y += panel_bg_padding * 2;
1014 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1015 if(panel.current_panel_bg != "0")
1016 end_pos.y += panel_bg_border * 2;
1018 if(panel_bg_padding)
1020 panel_pos += '1 1 0' * panel_bg_padding;
1021 panel_size -= '2 2 0' * panel_bg_padding;
1025 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1029 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1031 pos.y += 1.25 * hud_fontsize.y;
1034 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1036 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1039 // print header row and highlight columns
1040 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1042 // fill the table and draw the rows
1043 bool is_self = false;
1044 bool self_shown = false;
1046 for(pl = players.sort_next; pl; pl = pl.sort_next)
1048 if(pl.team != tm.team)
1050 if(i == max_players - 2 && pl != me)
1052 if(!self_shown && me.team == tm.team)
1054 Scoreboard_DrawItem(pos, rgb, me, true, i);
1056 pos.y += 1.25 * hud_fontsize.y;
1060 if(i >= max_players - 1)
1062 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1065 is_self = (pl.sv_entnum == current_player);
1066 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1069 pos.y += 1.25 * hud_fontsize.y;
1073 panel_size.x += panel_bg_padding * 2; // restore initial width
1077 bool Scoreboard_WouldDraw()
1079 if (QuickMenu_IsOpened())
1081 else if (HUD_Radar_Clickable())
1083 else if (scoreboard_showscores)
1085 else if (intermission == 1)
1087 else if (intermission == 2)
1089 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1091 else if (scoreboard_showscores_force)
1096 float average_accuracy;
1097 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1099 WepSet weapons_stat = WepSet_GetFromStat();
1100 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1101 int disownedcnt = 0;
1103 FOREACH(Weapons, it != WEP_Null, {
1104 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1106 WepSet set = it.m_wepset;
1107 if (weapon_stats < 0)
1109 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1111 else if (!(weapons_stat & set || weapons_inmap & set))
1116 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1117 if (weapon_cnt <= 0) return pos;
1120 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1122 int columnns = ceil(weapon_cnt / rows);
1124 float weapon_height = 29;
1125 float height = hud_fontsize.y + weapon_height;
1127 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1128 pos.y += 1.25 * hud_fontsize.y;
1129 if(panel.current_panel_bg != "0")
1130 pos.y += panel_bg_border;
1133 panel_size.y = height * rows;
1134 panel_size.y += panel_bg_padding * 2;
1137 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1138 if(panel.current_panel_bg != "0")
1139 end_pos.y += panel_bg_border * 2;
1141 if(panel_bg_padding)
1143 panel_pos += '1 1 0' * panel_bg_padding;
1144 panel_size -= '2 2 0' * panel_bg_padding;
1148 vector tmp = panel_size;
1150 float weapon_width = tmp.x / columnns / rows;
1153 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1157 // column highlighting
1158 for (int i = 0; i < columnns; ++i)
1160 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1163 for (int i = 0; i < rows; ++i)
1164 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1167 average_accuracy = 0;
1168 int weapons_with_stats = 0;
1170 pos.x += weapon_width / 2;
1172 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1175 Accuracy_LoadColors();
1177 float oldposx = pos.x;
1181 FOREACH(Weapons, it != WEP_Null, {
1182 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1184 WepSet set = it.m_wepset;
1185 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1189 if (weapon_stats >= 0)
1190 weapon_alpha = sbt_fg_alpha;
1192 weapon_alpha = 0.2 * sbt_fg_alpha;
1195 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1197 if (weapon_stats >= 0) {
1198 weapons_with_stats += 1;
1199 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1202 s = sprintf("%d%%", weapon_stats * 100);
1205 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1207 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1208 rgb = Accuracy_GetColor(weapon_stats);
1210 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1212 tmpos.x += weapon_width * rows;
1213 pos.x += weapon_width * rows;
1214 if (rows == 2 && column == columnns - 1) {
1222 if (weapons_with_stats)
1223 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1225 panel_size.x += panel_bg_padding * 2; // restore initial width
1229 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1231 pos.x += hud_fontsize.x * 0.25;
1232 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1233 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1234 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1236 pos.y += hud_fontsize.y;
1241 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1242 float stat_secrets_found, stat_secrets_total;
1243 float stat_monsters_killed, stat_monsters_total;
1247 // get monster stats
1248 stat_monsters_killed = STAT(MONSTERS_KILLED);
1249 stat_monsters_total = STAT(MONSTERS_TOTAL);
1251 // get secrets stats
1252 stat_secrets_found = STAT(SECRETS_FOUND);
1253 stat_secrets_total = STAT(SECRETS_TOTAL);
1255 // get number of rows
1256 if(stat_secrets_total)
1258 if(stat_monsters_total)
1261 // if no rows, return
1265 // draw table header
1266 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1267 pos.y += 1.25 * hud_fontsize.y;
1268 if(panel.current_panel_bg != "0")
1269 pos.y += panel_bg_border;
1272 panel_size.y = hud_fontsize.y * rows;
1273 panel_size.y += panel_bg_padding * 2;
1276 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1277 if(panel.current_panel_bg != "0")
1278 end_pos.y += panel_bg_border * 2;
1280 if(panel_bg_padding)
1282 panel_pos += '1 1 0' * panel_bg_padding;
1283 panel_size -= '2 2 0' * panel_bg_padding;
1287 vector tmp = panel_size;
1290 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1293 if(stat_monsters_total)
1295 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1296 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1300 if(stat_secrets_total)
1302 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1303 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1306 panel_size.x += panel_bg_padding * 2; // restore initial width
1311 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1314 RANKINGS_RECEIVED_CNT = 0;
1315 for (i=RANKINGS_CNT-1; i>=0; --i)
1317 ++RANKINGS_RECEIVED_CNT;
1319 if (RANKINGS_RECEIVED_CNT == 0)
1322 vector hl_rgb = rgb + '0.5 0.5 0.5';
1324 pos.y += hud_fontsize.y;
1325 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1326 pos.y += 1.25 * hud_fontsize.y;
1327 if(panel.current_panel_bg != "0")
1328 pos.y += panel_bg_border;
1333 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1335 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1340 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1342 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1346 float ranksize = 3 * hud_fontsize.x;
1347 float timesize = 5 * hud_fontsize.x;
1348 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1349 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1350 columns = min(columns, RANKINGS_RECEIVED_CNT);
1352 // expand name column to fill the entire row
1353 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1354 namesize += available_space;
1355 columnsize.x += available_space;
1357 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1358 panel_size.y += panel_bg_padding * 2;
1362 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1363 if(panel.current_panel_bg != "0")
1364 end_pos.y += panel_bg_border * 2;
1366 if(panel_bg_padding)
1368 panel_pos += '1 1 0' * panel_bg_padding;
1369 panel_size -= '2 2 0' * panel_bg_padding;
1375 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1377 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1379 int column = 0, j = 0;
1380 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1387 if(grecordholder[i] == entcs_GetName(player_localnum))
1388 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1389 else if(!((j + column) & 1) && sbt_highlight)
1390 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1392 str = count_ordinal(i+1);
1393 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1394 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1395 str = grecordholder[i];
1397 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1398 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1400 pos.y += 1.25 * hud_fontsize.y;
1402 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1406 pos.x += panel_size.x / columns;
1407 pos.y = panel_pos.y;
1411 panel_size.x += panel_bg_padding * 2; // restore initial width
1415 void Scoreboard_Draw()
1417 if(!autocvar__hud_configure)
1419 if(!hud_draw_maximized) return;
1421 // frametime checks allow to toggle the scoreboard even when the game is paused
1422 if(scoreboard_active) {
1423 if(hud_configure_menu_open == 1)
1424 scoreboard_fade_alpha = 1;
1425 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1426 if (scoreboard_fadeinspeed && frametime)
1427 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1429 scoreboard_fade_alpha = 1;
1430 if(hud_fontsize_str != autocvar_hud_fontsize)
1432 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1433 Scoreboard_initFieldSizes();
1434 if(hud_fontsize_str)
1435 strunzone(hud_fontsize_str);
1436 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1440 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1441 if (scoreboard_fadeoutspeed && frametime)
1442 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1444 scoreboard_fade_alpha = 0;
1447 if (!scoreboard_fade_alpha)
1451 scoreboard_fade_alpha = 0;
1453 if (autocvar_hud_panel_scoreboard_dynamichud)
1456 HUD_Scale_Disable();
1458 if(scoreboard_fade_alpha <= 0)
1460 panel_fade_alpha *= scoreboard_fade_alpha;
1461 HUD_Panel_LoadCvars();
1463 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1464 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1465 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1466 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1467 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1468 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1470 // don't overlap with con_notify
1471 if(!autocvar__hud_configure)
1472 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1474 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1475 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1476 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1477 panel_size.x = fixed_scoreboard_width;
1479 Scoreboard_UpdatePlayerTeams();
1481 vector pos = panel_pos;
1486 vector sb_heading_fontsize;
1487 sb_heading_fontsize = hud_fontsize * 2;
1488 draw_beginBoldFont();
1489 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1492 pos.y += sb_heading_fontsize.y;
1493 if(panel.current_panel_bg != "0")
1494 pos.y += panel_bg_border;
1496 // Draw the scoreboard
1497 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1500 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1504 vector panel_bg_color_save = panel_bg_color;
1505 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1506 if(panel.current_panel_bg != "0")
1507 team_score_baseoffset.x -= panel_bg_border;
1508 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1510 if(tm.team == NUM_SPECTATOR)
1515 draw_beginBoldFont();
1516 vector rgb = Team_ColorRGB(tm.team);
1517 str = ftos(tm.(teamscores(ts_primary)));
1518 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1520 if(ts_primary != ts_secondary)
1522 str = ftos(tm.(teamscores(ts_secondary)));
1523 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);
1526 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1527 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1528 else if(panel_bg_color_team > 0)
1529 panel_bg_color = rgb * panel_bg_color_team;
1531 panel_bg_color = rgb;
1532 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1534 panel_bg_color = panel_bg_color_save;
1538 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1539 if(tm.team != NUM_SPECTATOR)
1541 // display it anyway
1542 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1545 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1546 if(race_speedaward) {
1547 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);
1548 pos.y += 1.25 * hud_fontsize.y;
1550 if(race_speedaward_alltimebest) {
1551 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);
1552 pos.y += 1.25 * hud_fontsize.y;
1554 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1556 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1557 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1559 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1562 for(pl = players.sort_next; pl; pl = pl.sort_next)
1564 if(pl.team == NUM_SPECTATOR)
1566 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1567 if(tm.team == NUM_SPECTATOR)
1569 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1570 draw_beginBoldFont();
1571 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1573 pos.y += 1.25 * hud_fontsize.y;
1575 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1576 pos.y += 1.25 * hud_fontsize.y;
1582 // Print info string
1584 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1585 tl = STAT(TIMELIMIT);
1586 fl = STAT(FRAGLIMIT);
1587 ll = STAT(LEADLIMIT);
1588 if(gametype == MAPINFO_TYPE_LMS)
1591 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1596 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1600 str = strcat(str, _(" or"));
1603 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1604 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1605 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1606 TranslateScoresLabel(teamscores_label(ts_primary))));
1610 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1611 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1612 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1613 TranslateScoresLabel(scores_label(ps_primary))));
1618 if(tl > 0 || fl > 0)
1619 str = strcat(str, _(" or"));
1622 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1623 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1624 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1625 TranslateScoresLabel(teamscores_label(ts_primary))));
1629 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1630 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1631 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1632 TranslateScoresLabel(scores_label(ps_primary))));
1637 pos.y += 1.2 * hud_fontsize.y;
1638 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1640 // print information about respawn status
1641 float respawn_time = STAT(RESPAWN_TIME);
1645 if(respawn_time < 0)
1647 // a negative number means we are awaiting respawn, time value is still the same
1648 respawn_time *= -1; // remove mark now that we checked it
1650 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1651 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1653 str = sprintf(_("^1Respawning in ^3%s^1..."),
1654 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1655 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1657 count_seconds(ceil(respawn_time - time))
1661 else if(time < respawn_time)
1663 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1664 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1665 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1667 count_seconds(ceil(respawn_time - time))
1671 else if(time >= respawn_time)
1672 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1674 pos.y += 1.2 * hud_fontsize.y;
1675 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1678 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;