1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/net_linked.qh>
7 #include <common/mapinfo.qh>
8 #include <common/minigames/cl_minigames.qh>
9 #include <common/stats.qh>
10 #include <common/teams.qh>
14 const int MAX_SBT_FIELDS = MAX_SCORE;
16 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
17 float sbt_field_size[MAX_SBT_FIELDS + 1];
18 string sbt_field_title[MAX_SBT_FIELDS + 1];
21 string autocvar_hud_fontsize;
22 string hud_fontsize_str;
27 float sbt_fg_alpha_self;
29 float sbt_highlight_alpha;
30 float sbt_highlight_alpha_self;
32 // provide basic panel cvars to old clients
33 // TODO remove them after a future release (0.8.2+)
34 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
35 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
36 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
37 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
38 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
39 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
40 noref string autocvar_hud_panel_scoreboard_bg_border = "";
41 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
43 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
44 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
45 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
46 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
47 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
49 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
50 bool autocvar_hud_panel_scoreboard_table_highlight = true;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
52 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
53 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
54 float autocvar_hud_panel_scoreboard_namesize = 15;
56 bool autocvar_hud_panel_scoreboard_accuracy = true;
57 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
58 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
61 bool autocvar_hud_panel_scoreboard_dynamichud = false;
63 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
64 bool autocvar_hud_panel_scoreboard_others_showscore = true;
65 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
66 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
67 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
70 void drawstringright(vector, string, vector, vector, float, float);
71 void drawstringcenter(vector, string, vector, vector, float, float);
73 // wrapper to put all possible scores titles through gettext
74 string TranslateScoresLabel(string l)
78 case "bckills": return CTX(_("SCO^bckills"));
79 case "bctime": return CTX(_("SCO^bctime"));
80 case "caps": return CTX(_("SCO^caps"));
81 case "captime": return CTX(_("SCO^captime"));
82 case "deaths": return CTX(_("SCO^deaths"));
83 case "destroyed": return CTX(_("SCO^destroyed"));
84 case "dmg": return CTX(_("SCO^damage"));
85 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
86 case "drops": return CTX(_("SCO^drops"));
87 case "faults": return CTX(_("SCO^faults"));
88 case "fckills": return CTX(_("SCO^fckills"));
89 case "goals": return CTX(_("SCO^goals"));
90 case "kckills": return CTX(_("SCO^kckills"));
91 case "kdratio": return CTX(_("SCO^kdratio"));
92 case "kd": return CTX(_("SCO^k/d"));
93 case "kdr": return CTX(_("SCO^kdr"));
94 case "kills": return CTX(_("SCO^kills"));
95 case "laps": return CTX(_("SCO^laps"));
96 case "lives": return CTX(_("SCO^lives"));
97 case "losses": return CTX(_("SCO^losses"));
98 case "name": return CTX(_("SCO^name"));
99 case "sum": return CTX(_("SCO^sum"));
100 case "nick": return CTX(_("SCO^nick"));
101 case "objectives": return CTX(_("SCO^objectives"));
102 case "pickups": return CTX(_("SCO^pickups"));
103 case "ping": return CTX(_("SCO^ping"));
104 case "pl": return CTX(_("SCO^pl"));
105 case "pushes": return CTX(_("SCO^pushes"));
106 case "rank": return CTX(_("SCO^rank"));
107 case "returns": return CTX(_("SCO^returns"));
108 case "revivals": return CTX(_("SCO^revivals"));
109 case "rounds": return CTX(_("SCO^rounds won"));
110 case "score": return CTX(_("SCO^score"));
111 case "suicides": return CTX(_("SCO^suicides"));
112 case "takes": return CTX(_("SCO^takes"));
113 case "ticks": return CTX(_("SCO^ticks"));
118 void Scoreboard_InitScores()
122 ps_primary = ps_secondary = NULL;
123 ts_primary = ts_secondary = -1;
124 FOREACH(Scores, true, {
125 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
126 if(f == SFL_SORT_PRIO_PRIMARY)
128 if(f == SFL_SORT_PRIO_SECONDARY)
131 if(ps_secondary == NULL)
132 ps_secondary = ps_primary;
134 for(i = 0; i < MAX_TEAMSCORE; ++i)
136 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
137 if(f == SFL_SORT_PRIO_PRIMARY)
139 if(f == SFL_SORT_PRIO_SECONDARY)
142 if(ts_secondary == -1)
143 ts_secondary = ts_primary;
145 Cmd_Scoreboard_SetFields(0);
148 float SetTeam(entity pl, float Team);
150 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
296 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
297 LOG_INFO(_("Usage:\n"));
298 LOG_INFO(_("^2scoreboard_columns_set default\n"));
299 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
300 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
301 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
304 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
305 LOG_INFO(_("^3ping^7 Ping time\n"));
306 LOG_INFO(_("^3pl^7 Packet loss\n"));
307 LOG_INFO(_("^3elo^7 Player ELO\n"));
308 LOG_INFO(_("^3kills^7 Number of kills\n"));
309 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
310 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
311 LOG_INFO(_("^3frags^7 kills - suicides\n"));
312 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
313 LOG_INFO(_("^3dmg^7 The total damage done\n"));
314 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
315 LOG_INFO(_("^3sum^7 frags - deaths\n"));
316 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
317 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
318 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
319 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
320 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
321 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
322 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
323 LOG_INFO(_("^3rank^7 Player rank\n"));
324 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
325 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
326 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
327 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
328 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
329 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
330 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
331 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
332 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
333 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
334 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
335 LOG_INFO(_("^3score^7 Total score\n"));
338 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
339 "of game types, then a slash, to make the field show up only in these\n"
340 "or in all but these game types. You can also specify 'all' as a\n"
341 "field to show all fields available for the current game mode.\n\n"));
343 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
344 "include/exclude ALL teams/noteams game modes.\n\n"));
346 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
347 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
348 "right of the vertical bar aligned to the right.\n"));
349 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
350 "other gamemodes except DM.\n"));
353 // NOTE: adding a gametype with ? to not warn for an optional field
354 // make sure it's excluded in a previous exclusive rule, if any
355 // otherwise the previous exclusive rule warns anyway
356 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
357 #define SCOREBOARD_DEFAULT_COLUMNS \
359 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
360 " -teams,lms/deaths +ft,tdm/deaths" \
361 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
362 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
363 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
364 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
365 " +lms/lives +lms/rank" \
366 " +kh/caps +kh/pushes +kh/destroyed" \
367 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
368 " +as/objectives +nb/faults +nb/goals" \
369 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
370 " -lms,rc,cts,inv,nb/score"
372 void Cmd_Scoreboard_SetFields(int argc)
377 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
381 return; // do nothing, we don't know gametype and scores yet
383 // sbt_fields uses strunzone on the titles!
384 if(!sbt_field_title[0])
385 for(i = 0; i < MAX_SBT_FIELDS; ++i)
386 sbt_field_title[i] = strzone("(null)");
388 // TODO: re enable with gametype dependant cvars?
389 if(argc < 3) // no arguments provided
390 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
393 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 if(argv(2) == "default")
398 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
399 else if(argv(2) == "all")
402 s = "ping pl name |";
403 FOREACH(Scores, true, {
405 if(it != ps_secondary)
406 if(scores_label(it) != "")
407 s = strcat(s, " ", scores_label(it));
409 if(ps_secondary != ps_primary)
410 s = strcat(s, " ", scores_label(ps_secondary));
411 s = strcat(s, " ", scores_label(ps_primary));
412 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
419 hud_fontsize = HUD_GetFontsize("hud_fontsize");
421 for(i = 1; i < argc - 1; ++i)
427 if(substring(str, 0, 1) == "?")
430 str = substring(str, 1, strlen(str) - 1);
433 slash = strstrofs(str, "/", 0);
436 pattern = substring(str, 0, slash);
437 str = substring(str, slash + 1, strlen(str) - (slash + 1));
439 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
443 strunzone(sbt_field_title[sbt_num_fields]);
444 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
445 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
446 str = strtolower(str);
451 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
452 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
453 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
454 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
455 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
456 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
457 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
458 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
459 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
462 FOREACH(Scores, true, {
463 if (str == strtolower(scores_label(it))) {
465 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
475 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
479 sbt_field[sbt_num_fields] = j;
482 if(j == ps_secondary)
483 have_secondary = true;
488 if(sbt_num_fields >= MAX_SBT_FIELDS)
492 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
494 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
495 have_secondary = true;
496 if(ps_primary == ps_secondary)
497 have_secondary = true;
498 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
500 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
504 strunzone(sbt_field_title[sbt_num_fields]);
505 for(i = sbt_num_fields; i > 0; --i)
507 sbt_field_title[i] = sbt_field_title[i-1];
508 sbt_field_size[i] = sbt_field_size[i-1];
509 sbt_field[i] = sbt_field[i-1];
511 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
512 sbt_field[0] = SP_NAME;
514 LOG_INFO("fixed missing field 'name'\n");
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 1; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[1] = strzone("|");
526 sbt_field[1] = SP_SEPARATOR;
527 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
529 LOG_INFO("fixed missing field '|'\n");
532 else if(!have_separator)
534 strunzone(sbt_field_title[sbt_num_fields]);
535 sbt_field_title[sbt_num_fields] = strzone("|");
536 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
537 sbt_field[sbt_num_fields] = SP_SEPARATOR;
539 LOG_INFO("fixed missing field '|'\n");
543 strunzone(sbt_field_title[sbt_num_fields]);
544 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
545 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
546 sbt_field[sbt_num_fields] = ps_secondary;
548 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
552 strunzone(sbt_field_title[sbt_num_fields]);
553 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_primary;
557 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
561 sbt_field[sbt_num_fields] = SP_END;
565 vector sbt_field_rgb;
566 string sbt_field_icon0;
567 string sbt_field_icon1;
568 string sbt_field_icon2;
569 vector sbt_field_icon0_rgb;
570 vector sbt_field_icon1_rgb;
571 vector sbt_field_icon2_rgb;
572 string Scoreboard_GetName(entity pl)
574 if(ready_waiting && pl.ready)
576 sbt_field_icon0 = "gfx/scoreboard/player_ready";
580 int f = entcs_GetClientColors(pl.sv_entnum);
582 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
583 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
584 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
585 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
586 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
589 return entcs_GetName(pl.sv_entnum);
591 string Scoreboard_GetField(entity pl, PlayerScoreField field)
593 float tmp, num, denom;
596 sbt_field_rgb = '1 1 1';
597 sbt_field_icon0 = "";
598 sbt_field_icon1 = "";
599 sbt_field_icon2 = "";
600 sbt_field_icon0_rgb = '1 1 1';
601 sbt_field_icon1_rgb = '1 1 1';
602 sbt_field_icon2_rgb = '1 1 1';
607 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
608 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
612 tmp = max(0, min(220, f-80)) / 220;
613 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
619 f = pl.ping_packetloss;
620 tmp = pl.ping_movementloss;
621 if(f == 0 && tmp == 0)
623 str = ftos(ceil(f * 100));
625 str = strcat(str, "~", ftos(ceil(tmp * 100)));
626 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
627 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
631 return Scoreboard_GetName(pl);
634 f = pl.(scores(SP_KILLS));
635 f -= pl.(scores(SP_SUICIDES));
639 num = pl.(scores(SP_KILLS));
640 denom = pl.(scores(SP_DEATHS));
643 sbt_field_rgb = '0 1 0';
644 str = sprintf("%d", num);
645 } else if(num <= 0) {
646 sbt_field_rgb = '1 0 0';
647 str = sprintf("%.1f", num/denom);
649 str = sprintf("%.1f", num/denom);
653 f = pl.(scores(SP_KILLS));
654 f -= pl.(scores(SP_DEATHS));
657 sbt_field_rgb = '0 1 0';
659 sbt_field_rgb = '1 1 1';
661 sbt_field_rgb = '1 0 0';
667 float elo = pl.(scores(SP_ELO));
669 case -1: return "...";
670 case -2: return _("N/A");
671 default: return ftos(elo);
675 case SP_DMG: case SP_DMGTAKEN:
676 return sprintf("%.1f k", pl.(scores(field)) / 1000);
678 default: case SP_SCORE:
679 tmp = pl.(scores(field));
680 f = scores_flags(field);
681 if(field == ps_primary)
682 sbt_field_rgb = '1 1 0';
683 else if(field == ps_secondary)
684 sbt_field_rgb = '0 1 1';
686 sbt_field_rgb = '1 1 1';
687 return ScoreString(f, tmp);
692 float sbt_fixcolumnwidth_len;
693 float sbt_fixcolumnwidth_iconlen;
694 float sbt_fixcolumnwidth_marginlen;
696 string Scoreboard_FixColumnWidth(int i, string str)
702 sbt_fixcolumnwidth_iconlen = 0;
704 if(sbt_field_icon0 != "")
706 sz = draw_getimagesize(sbt_field_icon0);
708 if(sbt_fixcolumnwidth_iconlen < f)
709 sbt_fixcolumnwidth_iconlen = f;
712 if(sbt_field_icon1 != "")
714 sz = draw_getimagesize(sbt_field_icon1);
716 if(sbt_fixcolumnwidth_iconlen < f)
717 sbt_fixcolumnwidth_iconlen = f;
720 if(sbt_field_icon2 != "")
722 sz = draw_getimagesize(sbt_field_icon2);
724 if(sbt_fixcolumnwidth_iconlen < f)
725 sbt_fixcolumnwidth_iconlen = f;
728 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
730 if(sbt_fixcolumnwidth_iconlen != 0)
731 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
733 sbt_fixcolumnwidth_marginlen = 0;
735 if(sbt_field[i] == SP_NAME) // name gets all remaining space
738 float remaining_space = 0;
739 for(j = 0; j < sbt_num_fields; ++j)
741 if (sbt_field[i] != SP_SEPARATOR)
742 remaining_space += sbt_field_size[j] + hud_fontsize.x;
743 sbt_field_size[i] = panel_size.x - remaining_space;
745 if (sbt_fixcolumnwidth_iconlen != 0)
746 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
747 float namesize = panel_size.x - remaining_space;
748 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
749 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
751 max_namesize = vid_conwidth - remaining_space;
754 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
756 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
757 if(sbt_field_size[i] < f)
758 sbt_field_size[i] = f;
763 void Scoreboard_initFieldSizes()
765 for(int i = 0; i < sbt_num_fields; ++i)
767 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
768 Scoreboard_FixColumnWidth(i, "");
772 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
775 vector column_dim = eY * panel_size.y;
777 column_dim.y -= 1.25 * hud_fontsize.y;
778 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
779 pos.x += hud_fontsize.x * 0.5;
780 for(i = 0; i < sbt_num_fields; ++i)
782 if(sbt_field[i] == SP_SEPARATOR)
784 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
787 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
788 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
789 pos.x += column_dim.x;
791 if(sbt_field[i] == SP_SEPARATOR)
793 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
794 for(i = sbt_num_fields - 1; i > 0; --i)
796 if(sbt_field[i] == SP_SEPARATOR)
799 pos.x -= sbt_field_size[i];
804 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
805 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
808 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
809 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
810 pos.x -= hud_fontsize.x;
815 pos.y += 1.25 * hud_fontsize.y;
819 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
821 TC(bool, is_self); TC(int, pl_number);
823 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
825 vector h_pos = item_pos;
826 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
827 // alternated rows highlighting
829 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
830 else if((sbt_highlight) && (!(pl_number % 2)))
831 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
833 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
835 vector pos = item_pos;
836 vector pic_size = vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y);
837 pos.x += hud_fontsize.x * 0.5;
838 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
839 vector tmp = '0 0 0';
841 PlayerScoreField field;
842 for(i = 0; i < sbt_num_fields; ++i)
844 field = sbt_field[i];
845 if(field == SP_SEPARATOR)
848 if(is_spec && field != SP_NAME && field != SP_PING) {
849 pos.x += sbt_field_size[i] + hud_fontsize.x;
852 str = Scoreboard_GetField(pl, field);
853 str = Scoreboard_FixColumnWidth(i, str);
855 pos.x += sbt_field_size[i] + hud_fontsize.x;
857 if(field == SP_NAME) {
858 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
859 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
861 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
862 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
865 tmp.x = sbt_field_size[i] + hud_fontsize.x;
866 if(sbt_field_icon0 != "")
867 drawpic(pos - tmp, sbt_field_icon0, pic_size, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
868 if(sbt_field_icon1 != "")
869 drawpic(pos - tmp, sbt_field_icon1, pic_size, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
870 if(sbt_field_icon2 != "")
871 drawpic(pos - tmp, sbt_field_icon2, pic_size, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
874 if(sbt_field[i] == SP_SEPARATOR)
876 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
877 for(i = sbt_num_fields-1; i > 0; --i)
879 field = sbt_field[i];
880 if(field == SP_SEPARATOR)
883 if(is_spec && field != SP_NAME && field != SP_PING) {
884 pos.x -= sbt_field_size[i] + hud_fontsize.x;
888 str = Scoreboard_GetField(pl, field);
889 str = Scoreboard_FixColumnWidth(i, str);
891 if(field == SP_NAME) {
892 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
893 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
895 tmp.x = sbt_fixcolumnwidth_len;
896 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
899 tmp.x = sbt_field_size[i];
900 if(sbt_field_icon0 != "")
901 drawpic(pos - tmp, sbt_field_icon0, pic_size, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
902 if(sbt_field_icon1 != "")
903 drawpic(pos - tmp, sbt_field_icon1, pic_size, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
904 if(sbt_field_icon2 != "")
905 drawpic(pos - tmp, sbt_field_icon2, pic_size, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 pos.x -= sbt_field_size[i] + hud_fontsize.x;
911 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
914 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
917 vector h_pos = item_pos;
918 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
920 bool complete = (this_team == NUM_SPECTATOR);
923 if((sbt_highlight) && (!(pl_number % 2)))
924 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
926 vector pos = item_pos;
927 pos.x += hud_fontsize.x * 0.5;
928 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
930 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
932 width_limit -= stringwidth("...", false, hud_fontsize);
933 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
934 static float max_name_width = 0;
937 float min_fieldsize = 0;
938 float fieldpadding = hud_fontsize.x * 0.25;
939 if(this_team == NUM_SPECTATOR)
941 if(autocvar_hud_panel_scoreboard_spectators_showping)
942 min_fieldsize = stringwidth("999", false, hud_fontsize);
944 else if(autocvar_hud_panel_scoreboard_others_showscore)
945 min_fieldsize = stringwidth("99", false, hud_fontsize);
946 for(i = 0; pl; pl = pl.sort_next)
948 if(pl.team != this_team)
954 if(this_team == NUM_SPECTATOR)
956 if(autocvar_hud_panel_scoreboard_spectators_showping)
957 field = Scoreboard_GetField(pl, SP_PING);
959 else if(autocvar_hud_panel_scoreboard_others_showscore)
960 field = Scoreboard_GetField(pl, SP_SCORE);
962 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
963 float column_width = stringwidth(str, true, hud_fontsize);
964 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
966 if(column_width > max_name_width)
967 max_name_width = column_width;
968 column_width = max_name_width;
972 fieldsize = stringwidth(field, false, hud_fontsize);
973 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
976 if(pos.x + column_width > width_limit)
981 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
986 pos.x = item_pos.x + hud_fontsize.x * 0.5;
987 pos.y += hud_fontsize.y * 1.25;
991 vector name_pos = pos;
992 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
993 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
994 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
997 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
998 h_size.y = hud_fontsize.y;
999 vector field_pos = pos;
1000 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1001 field_pos.x += column_width - h_size.x;
1003 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1004 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1005 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1007 pos.x += column_width;
1008 pos.x += hud_fontsize.x;
1010 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1013 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1015 int max_players = 999;
1016 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1018 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1021 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1022 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1023 height /= team_count;
1026 height -= panel_bg_padding * 2; // - padding
1027 max_players = floor(height / (hud_fontsize.y * 1.25));
1028 if(max_players <= 1)
1030 if(max_players == tm.team_size)
1035 entity me = playerslots[current_player];
1037 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1038 panel_size.y += panel_bg_padding * 2;
1041 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1042 if(panel.current_panel_bg != "0")
1043 end_pos.y += panel_bg_border * 2;
1045 if(panel_bg_padding)
1047 panel_pos += '1 1 0' * panel_bg_padding;
1048 panel_size -= '2 2 0' * panel_bg_padding;
1052 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1056 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1058 pos.y += 1.25 * hud_fontsize.y;
1061 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1063 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1066 // print header row and highlight columns
1067 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1069 // fill the table and draw the rows
1070 bool is_self = false;
1071 bool self_shown = false;
1073 for(pl = players.sort_next; pl; pl = pl.sort_next)
1075 if(pl.team != tm.team)
1077 if(i == max_players - 2 && pl != me)
1079 if(!self_shown && me.team == tm.team)
1081 Scoreboard_DrawItem(pos, rgb, me, true, i);
1083 pos.y += 1.25 * hud_fontsize.y;
1087 if(i >= max_players - 1)
1089 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1092 is_self = (pl.sv_entnum == current_player);
1093 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1096 pos.y += 1.25 * hud_fontsize.y;
1100 panel_size.x += panel_bg_padding * 2; // restore initial width
1104 bool Scoreboard_WouldDraw()
1106 if (MUTATOR_CALLHOOK(DrawScoreboard))
1108 else if (QuickMenu_IsOpened())
1110 else if (HUD_Radar_Clickable())
1112 else if (scoreboard_showscores)
1114 else if (intermission == 1)
1116 else if (intermission == 2)
1118 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1120 else if (scoreboard_showscores_force)
1125 float average_accuracy;
1126 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1128 WepSet weapons_stat = WepSet_GetFromStat();
1129 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1130 int disownedcnt = 0;
1132 FOREACH(Weapons, it != WEP_Null, {
1133 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1135 WepSet set = it.m_wepset;
1136 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1138 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1145 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1146 if (weapon_cnt <= 0) return pos;
1149 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1151 int columnns = ceil(weapon_cnt / rows);
1153 float weapon_height = 29;
1154 float height = hud_fontsize.y + weapon_height;
1156 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1157 pos.y += 1.25 * hud_fontsize.y;
1158 if(panel.current_panel_bg != "0")
1159 pos.y += panel_bg_border;
1162 panel_size.y = height * rows;
1163 panel_size.y += panel_bg_padding * 2;
1166 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1167 if(panel.current_panel_bg != "0")
1168 end_pos.y += panel_bg_border * 2;
1170 if(panel_bg_padding)
1172 panel_pos += '1 1 0' * panel_bg_padding;
1173 panel_size -= '2 2 0' * panel_bg_padding;
1177 vector tmp = panel_size;
1179 float weapon_width = tmp.x / columnns / rows;
1182 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1186 // column highlighting
1187 for (int i = 0; i < columnns; ++i)
1189 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1192 for (int i = 0; i < rows; ++i)
1193 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1196 average_accuracy = 0;
1197 int weapons_with_stats = 0;
1199 pos.x += weapon_width / 2;
1201 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1204 Accuracy_LoadColors();
1206 float oldposx = pos.x;
1210 FOREACH(Weapons, it != WEP_Null, {
1211 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1213 WepSet set = it.m_wepset;
1214 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1218 if (weapon_stats >= 0)
1219 weapon_alpha = sbt_fg_alpha;
1221 weapon_alpha = 0.2 * sbt_fg_alpha;
1224 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1226 if (weapon_stats >= 0) {
1227 weapons_with_stats += 1;
1228 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1231 s = sprintf("%d%%", weapon_stats * 100);
1234 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1236 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1237 rgb = Accuracy_GetColor(weapon_stats);
1239 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1241 tmpos.x += weapon_width * rows;
1242 pos.x += weapon_width * rows;
1243 if (rows == 2 && column == columnns - 1) {
1251 if (weapons_with_stats)
1252 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1254 panel_size.x += panel_bg_padding * 2; // restore initial width
1258 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1260 pos.x += hud_fontsize.x * 0.25;
1261 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1262 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1263 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1265 pos.y += hud_fontsize.y;
1270 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1271 float stat_secrets_found, stat_secrets_total;
1272 float stat_monsters_killed, stat_monsters_total;
1276 // get monster stats
1277 stat_monsters_killed = STAT(MONSTERS_KILLED);
1278 stat_monsters_total = STAT(MONSTERS_TOTAL);
1280 // get secrets stats
1281 stat_secrets_found = STAT(SECRETS_FOUND);
1282 stat_secrets_total = STAT(SECRETS_TOTAL);
1284 // get number of rows
1285 if(stat_secrets_total)
1287 if(stat_monsters_total)
1290 // if no rows, return
1294 // draw table header
1295 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1296 pos.y += 1.25 * hud_fontsize.y;
1297 if(panel.current_panel_bg != "0")
1298 pos.y += panel_bg_border;
1301 panel_size.y = hud_fontsize.y * rows;
1302 panel_size.y += panel_bg_padding * 2;
1305 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1306 if(panel.current_panel_bg != "0")
1307 end_pos.y += panel_bg_border * 2;
1309 if(panel_bg_padding)
1311 panel_pos += '1 1 0' * panel_bg_padding;
1312 panel_size -= '2 2 0' * panel_bg_padding;
1316 vector tmp = panel_size;
1319 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1322 if(stat_monsters_total)
1324 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1325 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1329 if(stat_secrets_total)
1331 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1332 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1335 panel_size.x += panel_bg_padding * 2; // restore initial width
1340 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1343 RANKINGS_RECEIVED_CNT = 0;
1344 for (i=RANKINGS_CNT-1; i>=0; --i)
1346 ++RANKINGS_RECEIVED_CNT;
1348 if (RANKINGS_RECEIVED_CNT == 0)
1351 vector hl_rgb = rgb + '0.5 0.5 0.5';
1353 pos.y += hud_fontsize.y;
1354 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1355 pos.y += 1.25 * hud_fontsize.y;
1356 if(panel.current_panel_bg != "0")
1357 pos.y += panel_bg_border;
1362 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1364 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1369 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1371 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1375 float ranksize = 3 * hud_fontsize.x;
1376 float timesize = 5 * hud_fontsize.x;
1377 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1378 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1379 columns = min(columns, RANKINGS_RECEIVED_CNT);
1381 // expand name column to fill the entire row
1382 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1383 namesize += available_space;
1384 columnsize.x += available_space;
1386 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1387 panel_size.y += panel_bg_padding * 2;
1391 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1392 if(panel.current_panel_bg != "0")
1393 end_pos.y += panel_bg_border * 2;
1395 if(panel_bg_padding)
1397 panel_pos += '1 1 0' * panel_bg_padding;
1398 panel_size -= '2 2 0' * panel_bg_padding;
1404 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1406 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1408 int column = 0, j = 0;
1409 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1416 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1417 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1418 else if(!((j + column) & 1) && sbt_highlight)
1419 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1421 str = count_ordinal(i+1);
1422 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1423 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1424 str = ColorTranslateRGB(grecordholder[i]);
1426 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1427 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1429 pos.y += 1.25 * hud_fontsize.y;
1431 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1435 pos.x += panel_size.x / columns;
1436 pos.y = panel_pos.y;
1440 panel_size.x += panel_bg_padding * 2; // restore initial width
1444 void Scoreboard_Draw()
1446 if(!autocvar__hud_configure)
1448 if(!hud_draw_maximized) return;
1450 // frametime checks allow to toggle the scoreboard even when the game is paused
1451 if(scoreboard_active) {
1452 if(hud_configure_menu_open == 1)
1453 scoreboard_fade_alpha = 1;
1454 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1455 if (scoreboard_fadeinspeed && frametime)
1456 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1458 scoreboard_fade_alpha = 1;
1459 if(hud_fontsize_str != autocvar_hud_fontsize)
1461 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1462 Scoreboard_initFieldSizes();
1463 if(hud_fontsize_str)
1464 strunzone(hud_fontsize_str);
1465 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1469 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1470 if (scoreboard_fadeoutspeed && frametime)
1471 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1473 scoreboard_fade_alpha = 0;
1476 if (!scoreboard_fade_alpha)
1480 scoreboard_fade_alpha = 0;
1482 if (autocvar_hud_panel_scoreboard_dynamichud)
1485 HUD_Scale_Disable();
1487 if(scoreboard_fade_alpha <= 0)
1489 panel_fade_alpha *= scoreboard_fade_alpha;
1490 HUD_Panel_LoadCvars();
1492 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1493 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1494 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1495 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1496 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1497 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1499 // don't overlap with con_notify
1500 if(!autocvar__hud_configure)
1501 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1503 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1504 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1505 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1506 panel_size.x = fixed_scoreboard_width;
1508 Scoreboard_UpdatePlayerTeams();
1510 vector pos = panel_pos;
1516 vector sb_heading_fontsize;
1517 sb_heading_fontsize = hud_fontsize * 2;
1518 draw_beginBoldFont();
1519 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1522 pos.y += sb_heading_fontsize.y;
1523 if(panel.current_panel_bg != "0")
1524 pos.y += panel_bg_border;
1526 // Draw the scoreboard
1527 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1530 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1534 vector panel_bg_color_save = panel_bg_color;
1535 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1536 if(panel.current_panel_bg != "0")
1537 team_score_baseoffset.x -= panel_bg_border;
1538 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1540 if(tm.team == NUM_SPECTATOR)
1545 draw_beginBoldFont();
1546 vector rgb = Team_ColorRGB(tm.team);
1547 str = ftos(tm.(teamscores(ts_primary)));
1548 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1549 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1551 if(ts_primary != ts_secondary)
1553 str = ftos(tm.(teamscores(ts_secondary)));
1554 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1555 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1558 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1559 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1560 else if(panel_bg_color_team > 0)
1561 panel_bg_color = rgb * panel_bg_color_team;
1563 panel_bg_color = rgb;
1564 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1566 panel_bg_color = panel_bg_color_save;
1570 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1571 if(tm.team != NUM_SPECTATOR)
1573 // display it anyway
1574 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1577 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1579 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1580 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1582 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1583 if(race_speedaward) {
1584 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1585 pos.y += 1.25 * hud_fontsize.y;
1587 if(race_speedaward_alltimebest) {
1588 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1589 pos.y += 1.25 * hud_fontsize.y;
1591 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1594 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1597 for(pl = players.sort_next; pl; pl = pl.sort_next)
1599 if(pl.team == NUM_SPECTATOR)
1601 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1602 if(tm.team == NUM_SPECTATOR)
1604 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1605 draw_beginBoldFont();
1606 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1608 pos.y += 1.25 * hud_fontsize.y;
1610 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1611 pos.y += 1.25 * hud_fontsize.y;
1617 // Print info string
1619 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1620 tl = STAT(TIMELIMIT);
1621 fl = STAT(FRAGLIMIT);
1622 ll = STAT(LEADLIMIT);
1623 if(gametype == MAPINFO_TYPE_LMS)
1626 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1631 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1635 str = strcat(str, _(" or"));
1638 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1639 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1640 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1641 TranslateScoresLabel(teamscores_label(ts_primary))));
1645 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1646 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1647 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648 TranslateScoresLabel(scores_label(ps_primary))));
1653 if(tl > 0 || fl > 0)
1654 str = strcat(str, _(" or"));
1657 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1658 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1659 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1660 TranslateScoresLabel(teamscores_label(ts_primary))));
1664 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1665 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1666 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1667 TranslateScoresLabel(scores_label(ps_primary))));
1672 pos.y += 1.2 * hud_fontsize.y;
1673 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1675 // print information about respawn status
1676 float respawn_time = STAT(RESPAWN_TIME);
1680 if(respawn_time < 0)
1682 // a negative number means we are awaiting respawn, time value is still the same
1683 respawn_time *= -1; // remove mark now that we checked it
1685 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1686 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1688 str = sprintf(_("^1Respawning in ^3%s^1..."),
1689 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1690 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1692 count_seconds(ceil(respawn_time - time))
1696 else if(time < respawn_time)
1698 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1699 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1700 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1702 count_seconds(ceil(respawn_time - time))
1706 else if(time >= respawn_time)
1707 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1709 pos.y += 1.2 * hud_fontsize.y;
1710 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1713 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;