1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
13 const int MAX_SBT_FIELDS = MAX_SCORE;
15 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
16 float sbt_field_size[MAX_SBT_FIELDS + 1];
17 string sbt_field_title[MAX_SBT_FIELDS + 1];
20 string autocvar_hud_fontsize;
21 string hud_fontsize_str;
26 float sbt_fg_alpha_self;
28 float sbt_highlight_alpha;
29 float sbt_highlight_alpha_self;
31 // provide basic panel cvars to old clients
32 // TODO remove them after a future release (0.8.2+)
33 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
34 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
35 string autocvar_hud_panel_scoreboard_bg = "border_default";
36 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
37 string autocvar_hud_panel_scoreboard_bg_color_team = "";
38 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
39 string autocvar_hud_panel_scoreboard_bg_border = "";
40 string autocvar_hud_panel_scoreboard_bg_padding = "";
42 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
43 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
44 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
45 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
46 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
49 bool autocvar_hud_panel_scoreboard_table_highlight = true;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
52 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 float autocvar_hud_panel_scoreboard_namesize = 15;
55 bool autocvar_hud_panel_scoreboard_accuracy = true;
56 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
57 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_dynamichud = false;
61 bool autocvar_hud_panel_scoreboard_maxheight = 0.5;
62 bool autocvar_hud_panel_scoreboard_others_showscore = true;
63 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
66 void drawstringright(vector, string, vector, vector, float, float);
67 void drawstringcenter(vector, string, vector, vector, float, float);
69 // wrapper to put all possible scores titles through gettext
70 string TranslateScoresLabel(string l)
74 case "bckills": return CTX(_("SCO^bckills"));
75 case "bctime": return CTX(_("SCO^bctime"));
76 case "caps": return CTX(_("SCO^caps"));
77 case "captime": return CTX(_("SCO^captime"));
78 case "deaths": return CTX(_("SCO^deaths"));
79 case "destroyed": return CTX(_("SCO^destroyed"));
80 case "dmg": return CTX(_("SCO^damage"));
81 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
82 case "drops": return CTX(_("SCO^drops"));
83 case "faults": return CTX(_("SCO^faults"));
84 case "fckills": return CTX(_("SCO^fckills"));
85 case "goals": return CTX(_("SCO^goals"));
86 case "kckills": return CTX(_("SCO^kckills"));
87 case "kdratio": return CTX(_("SCO^kdratio"));
88 case "kd": return CTX(_("SCO^k/d"));
89 case "kdr": return CTX(_("SCO^kdr"));
90 case "kills": return CTX(_("SCO^kills"));
91 case "laps": return CTX(_("SCO^laps"));
92 case "lives": return CTX(_("SCO^lives"));
93 case "losses": return CTX(_("SCO^losses"));
94 case "name": return CTX(_("SCO^name"));
95 case "sum": return CTX(_("SCO^sum"));
96 case "nick": return CTX(_("SCO^nick"));
97 case "objectives": return CTX(_("SCO^objectives"));
98 case "pickups": return CTX(_("SCO^pickups"));
99 case "ping": return CTX(_("SCO^ping"));
100 case "pl": return CTX(_("SCO^pl"));
101 case "pushes": return CTX(_("SCO^pushes"));
102 case "rank": return CTX(_("SCO^rank"));
103 case "returns": return CTX(_("SCO^returns"));
104 case "revivals": return CTX(_("SCO^revivals"));
105 case "rounds": return CTX(_("SCO^rounds won"));
106 case "score": return CTX(_("SCO^score"));
107 case "suicides": return CTX(_("SCO^suicides"));
108 case "takes": return CTX(_("SCO^takes"));
109 case "ticks": return CTX(_("SCO^ticks"));
114 void Scoreboard_InitScores()
118 ps_primary = ps_secondary = NULL;
119 ts_primary = ts_secondary = -1;
120 FOREACH(Scores, true, {
121 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
122 if(f == SFL_SORT_PRIO_PRIMARY)
124 if(f == SFL_SORT_PRIO_SECONDARY)
127 if(ps_secondary == NULL)
128 ps_secondary = ps_primary;
130 for(i = 0; i < MAX_TEAMSCORE; ++i)
132 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
133 if(f == SFL_SORT_PRIO_PRIMARY)
135 if(f == SFL_SORT_PRIO_SECONDARY)
138 if(ts_secondary == -1)
139 ts_secondary = ts_primary;
141 Cmd_Scoreboard_SetFields(0);
144 float SetTeam(entity pl, float Team);
146 void Scoreboard_UpdatePlayerTeams()
151 for(pl = players.sort_next; pl; pl = pl.sort_next)
154 Team = entcs_GetScoreTeam(pl.sv_entnum);
155 if(SetTeam(pl, Team))
158 Scoreboard_UpdatePlayerPos(pl);
162 pl = players.sort_next;
167 print(strcat("PNUM: ", ftos(num), "\n"));
172 int Scoreboard_CompareScore(int vl, int vr, int f)
174 TC(int, vl); TC(int, vr); TC(int, f);
175 if(f & SFL_ZERO_IS_WORST)
177 if(vl == 0 && vr != 0)
179 if(vl != 0 && vr == 0)
183 return IS_INCREASING(f);
185 return IS_DECREASING(f);
189 float Scoreboard_ComparePlayerScores(entity left, entity right)
192 vl = entcs_GetTeam(left.sv_entnum);
193 vr = entcs_GetTeam(right.sv_entnum);
205 if(vl == NUM_SPECTATOR)
207 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
209 if(!left.gotscores && right.gotscores)
214 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
218 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
222 FOREACH(Scores, true, {
223 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
224 if (r >= 0) return r;
227 if (left.sv_entnum < right.sv_entnum)
233 void Scoreboard_UpdatePlayerPos(entity player)
236 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
238 SORT_SWAP(player, ent);
240 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
242 SORT_SWAP(ent, player);
246 float Scoreboard_CompareTeamScores(entity left, entity right)
250 if(left.team == NUM_SPECTATOR)
252 if(right.team == NUM_SPECTATOR)
255 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
259 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
263 for(i = 0; i < MAX_TEAMSCORE; ++i)
265 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
270 if (left.team < right.team)
276 void Scoreboard_UpdateTeamPos(entity Team)
279 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
281 SORT_SWAP(Team, ent);
283 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
285 SORT_SWAP(ent, Team);
289 void Cmd_Scoreboard_Help()
291 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
292 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
293 LOG_INFO(_("Usage:\n"));
294 LOG_INFO(_("^2scoreboard_columns_set default\n"));
295 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
296 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
297 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
300 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
301 LOG_INFO(_("^3ping^7 Ping time\n"));
302 LOG_INFO(_("^3pl^7 Packet loss\n"));
303 LOG_INFO(_("^3elo^7 Player ELO\n"));
304 LOG_INFO(_("^3kills^7 Number of kills\n"));
305 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
306 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
307 LOG_INFO(_("^3frags^7 kills - suicides\n"));
308 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
309 LOG_INFO(_("^3dmg^7 The total damage done\n"));
310 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
311 LOG_INFO(_("^3sum^7 frags - deaths\n"));
312 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
313 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
314 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
315 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
316 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
317 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
318 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
319 LOG_INFO(_("^3rank^7 Player rank\n"));
320 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
321 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
322 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
323 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
324 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
325 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
326 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
327 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
328 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
329 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
330 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
331 LOG_INFO(_("^3score^7 Total score\n"));
334 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
335 "of game types, then a slash, to make the field show up only in these\n"
336 "or in all but these game types. You can also specify 'all' as a\n"
337 "field to show all fields available for the current game mode.\n\n"));
339 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
340 "include/exclude ALL teams/noteams game modes.\n\n"));
342 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
343 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
344 "right of the vertical bar aligned to the right.\n"));
345 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
346 "other gamemodes except DM.\n"));
349 // NOTE: adding a gametype with ? to not warn for an optional field
350 // make sure it's excluded in a previous exclusive rule, if any
351 // otherwise the previous exclusive rule warns anyway
352 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
353 #define SCOREBOARD_DEFAULT_COLUMNS \
355 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
356 " -teams,lms/deaths +ft,tdm/deaths" \
357 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
358 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
359 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
360 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
361 " +lms/lives +lms/rank" \
362 " +kh/caps +kh/pushes +kh/destroyed" \
363 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
364 " +as/objectives +nb/faults +nb/goals" \
365 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
366 " -lms,rc,cts,inv,nb/score"
368 void Cmd_Scoreboard_SetFields(int argc)
373 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
377 return; // do nothing, we don't know gametype and scores yet
379 // sbt_fields uses strunzone on the titles!
380 if(!sbt_field_title[0])
381 for(i = 0; i < MAX_SBT_FIELDS; ++i)
382 sbt_field_title[i] = strzone("(null)");
384 // TODO: re enable with gametype dependant cvars?
385 if(argc < 3) // no arguments provided
386 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
389 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
393 if(argv(2) == "default")
394 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
395 else if(argv(2) == "all")
398 s = "ping pl name |";
399 FOREACH(Scores, true, {
401 if(it != ps_secondary)
402 if(scores_label(it) != "")
403 s = strcat(s, " ", scores_label(it));
405 if(ps_secondary != ps_primary)
406 s = strcat(s, " ", scores_label(ps_secondary));
407 s = strcat(s, " ", scores_label(ps_primary));
408 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
415 hud_fontsize = HUD_GetFontsize("hud_fontsize");
417 for(i = 1; i < argc - 1; ++i)
423 if(substring(str, 0, 1) == "?")
426 str = substring(str, 1, strlen(str) - 1);
429 slash = strstrofs(str, "/", 0);
432 pattern = substring(str, 0, slash);
433 str = substring(str, slash + 1, strlen(str) - (slash + 1));
435 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
439 strunzone(sbt_field_title[sbt_num_fields]);
440 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
441 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
442 str = strtolower(str);
447 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
448 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
449 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
450 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
451 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
452 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
453 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
454 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
455 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
458 FOREACH(Scores, true, {
459 if (str == strtolower(scores_label(it))) {
461 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
471 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
475 sbt_field[sbt_num_fields] = j;
478 if(j == ps_secondary)
479 have_secondary = true;
484 if(sbt_num_fields >= MAX_SBT_FIELDS)
488 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
490 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
491 have_secondary = true;
492 if(ps_primary == ps_secondary)
493 have_secondary = true;
494 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
496 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
500 strunzone(sbt_field_title[sbt_num_fields]);
501 for(i = sbt_num_fields; i > 0; --i)
503 sbt_field_title[i] = sbt_field_title[i-1];
504 sbt_field_size[i] = sbt_field_size[i-1];
505 sbt_field[i] = sbt_field[i-1];
507 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
508 sbt_field[0] = SP_NAME;
510 LOG_INFO("fixed missing field 'name'\n");
514 strunzone(sbt_field_title[sbt_num_fields]);
515 for(i = sbt_num_fields; i > 1; --i)
517 sbt_field_title[i] = sbt_field_title[i-1];
518 sbt_field_size[i] = sbt_field_size[i-1];
519 sbt_field[i] = sbt_field[i-1];
521 sbt_field_title[1] = strzone("|");
522 sbt_field[1] = SP_SEPARATOR;
523 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
525 LOG_INFO("fixed missing field '|'\n");
528 else if(!have_separator)
530 strunzone(sbt_field_title[sbt_num_fields]);
531 sbt_field_title[sbt_num_fields] = strzone("|");
532 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
533 sbt_field[sbt_num_fields] = SP_SEPARATOR;
535 LOG_INFO("fixed missing field '|'\n");
539 strunzone(sbt_field_title[sbt_num_fields]);
540 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
541 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
542 sbt_field[sbt_num_fields] = ps_secondary;
544 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
548 strunzone(sbt_field_title[sbt_num_fields]);
549 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
550 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
551 sbt_field[sbt_num_fields] = ps_primary;
553 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
557 sbt_field[sbt_num_fields] = SP_END;
561 vector sbt_field_rgb;
562 string sbt_field_icon0;
563 string sbt_field_icon1;
564 string sbt_field_icon2;
565 vector sbt_field_icon0_rgb;
566 vector sbt_field_icon1_rgb;
567 vector sbt_field_icon2_rgb;
568 string Scoreboard_GetField(entity pl, PlayerScoreField field)
570 float tmp, num, denom;
573 sbt_field_rgb = '1 1 1';
574 sbt_field_icon0 = "";
575 sbt_field_icon1 = "";
576 sbt_field_icon2 = "";
577 sbt_field_icon0_rgb = '1 1 1';
578 sbt_field_icon1_rgb = '1 1 1';
579 sbt_field_icon2_rgb = '1 1 1';
584 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
585 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
589 tmp = max(0, min(220, f-80)) / 220;
590 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
596 f = pl.ping_packetloss;
597 tmp = pl.ping_movementloss;
598 if(f == 0 && tmp == 0)
600 str = ftos(ceil(f * 100));
602 str = strcat(str, "~", ftos(ceil(tmp * 100)));
603 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
604 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
608 if(ready_waiting && pl.ready)
610 sbt_field_icon0 = "gfx/scoreboard/player_ready";
614 f = entcs_GetClientColors(pl.sv_entnum);
616 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
617 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
618 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
619 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
620 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
623 return entcs_GetName(pl.sv_entnum);
626 f = pl.(scores(SP_KILLS));
627 f -= pl.(scores(SP_SUICIDES));
631 num = pl.(scores(SP_KILLS));
632 denom = pl.(scores(SP_DEATHS));
635 sbt_field_rgb = '0 1 0';
636 str = sprintf("%d", num);
637 } else if(num <= 0) {
638 sbt_field_rgb = '1 0 0';
639 str = sprintf("%.1f", num/denom);
641 str = sprintf("%.1f", num/denom);
645 f = pl.(scores(SP_KILLS));
646 f -= pl.(scores(SP_DEATHS));
649 sbt_field_rgb = '0 1 0';
651 sbt_field_rgb = '1 1 1';
653 sbt_field_rgb = '1 0 0';
659 float elo = pl.(scores(SP_ELO));
661 case -1: return "...";
662 case -2: return _("N/A");
663 default: return ftos(elo);
667 case SP_DMG: case SP_DMGTAKEN:
668 return sprintf("%.1f k", pl.(scores(field)) / 1000);
671 tmp = pl.(scores(field));
672 f = scores_flags(field);
673 if(field == ps_primary)
674 sbt_field_rgb = '1 1 0';
675 else if(field == ps_secondary)
676 sbt_field_rgb = '0 1 1';
678 sbt_field_rgb = '1 1 1';
679 return ScoreString(f, tmp);
684 float sbt_fixcolumnwidth_len;
685 float sbt_fixcolumnwidth_iconlen;
686 float sbt_fixcolumnwidth_marginlen;
688 string Scoreboard_FixColumnWidth(int i, string str)
694 sbt_fixcolumnwidth_iconlen = 0;
696 if(sbt_field_icon0 != "")
698 sz = draw_getimagesize(sbt_field_icon0);
700 if(sbt_fixcolumnwidth_iconlen < f)
701 sbt_fixcolumnwidth_iconlen = f;
704 if(sbt_field_icon1 != "")
706 sz = draw_getimagesize(sbt_field_icon1);
708 if(sbt_fixcolumnwidth_iconlen < f)
709 sbt_fixcolumnwidth_iconlen = f;
712 if(sbt_field_icon2 != "")
714 sz = draw_getimagesize(sbt_field_icon2);
716 if(sbt_fixcolumnwidth_iconlen < f)
717 sbt_fixcolumnwidth_iconlen = f;
720 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
722 if(sbt_fixcolumnwidth_iconlen != 0)
723 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
725 sbt_fixcolumnwidth_marginlen = 0;
727 if(sbt_field[i] == SP_NAME) // name gets all remaining space
730 float remaining_space = 0;
731 for(j = 0; j < sbt_num_fields; ++j)
733 if (sbt_field[i] != SP_SEPARATOR)
734 remaining_space += sbt_field_size[j] + hud_fontsize.x;
735 sbt_field_size[i] = panel_size.x - remaining_space;
737 if (sbt_fixcolumnwidth_iconlen != 0)
738 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
739 float namesize = panel_size.x - remaining_space;
740 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
741 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
743 max_namesize = vid_conwidth - remaining_space;
746 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
748 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
749 if(sbt_field_size[i] < f)
750 sbt_field_size[i] = f;
755 void Scoreboard_initFieldSizes()
757 for(int i = 0; i < sbt_num_fields; ++i)
758 Scoreboard_FixColumnWidth(i, "");
761 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
764 vector column_dim = eY * panel_size.y;
766 column_dim.y -= 1.25 * hud_fontsize.y;
767 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
768 pos.x += hud_fontsize.x * 0.5;
769 for(i = 0; i < sbt_num_fields; ++i)
771 if(sbt_field[i] == SP_SEPARATOR)
773 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
776 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
777 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
778 pos.x += column_dim.x;
780 if(sbt_field[i] == SP_SEPARATOR)
782 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
783 for(i = sbt_num_fields - 1; i > 0; --i)
785 if(sbt_field[i] == SP_SEPARATOR)
788 pos.x -= sbt_field_size[i];
793 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
794 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
797 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
798 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
799 pos.x -= hud_fontsize.x;
804 pos.y += 1.25 * hud_fontsize.y;
808 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
810 TC(bool, is_self); TC(int, pl_number);
812 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
814 vector h_pos = item_pos;
815 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
816 // alternated rows highlighting
818 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
819 else if((sbt_highlight) && (!(pl_number % 2)))
820 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
822 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
824 vector pos = item_pos;
825 pos.x += hud_fontsize.x * 0.5;
826 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
827 vector tmp = '0 0 0';
829 PlayerScoreField field;
830 for(i = 0; i < sbt_num_fields; ++i)
832 field = sbt_field[i];
833 if(field == SP_SEPARATOR)
836 if(is_spec && field != SP_NAME && field != SP_PING) {
837 pos.x += sbt_field_size[i] + hud_fontsize.x;
840 str = Scoreboard_GetField(pl, field);
841 str = Scoreboard_FixColumnWidth(i, str);
843 pos.x += sbt_field_size[i] + hud_fontsize.x;
845 if(field == SP_NAME) {
846 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
847 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
849 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
850 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
853 tmp.x = sbt_field_size[i] + hud_fontsize.x;
854 if(sbt_field_icon0 != "")
855 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);
856 if(sbt_field_icon1 != "")
857 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);
858 if(sbt_field_icon2 != "")
859 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);
862 if(sbt_field[i] == SP_SEPARATOR)
864 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
865 for(i = sbt_num_fields-1; i > 0; --i)
867 field = sbt_field[i];
868 if(field == SP_SEPARATOR)
871 if(is_spec && field != SP_NAME && field != SP_PING) {
872 pos.x -= sbt_field_size[i] + hud_fontsize.x;
876 str = Scoreboard_GetField(pl, field);
877 str = Scoreboard_FixColumnWidth(i, str);
879 if(field == SP_NAME) {
880 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
881 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
883 tmp.x = sbt_fixcolumnwidth_len;
884 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_field_size[i];
888 if(sbt_field_icon0 != "")
889 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);
890 if(sbt_field_icon1 != "")
891 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);
892 if(sbt_field_icon2 != "")
893 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);
894 pos.x -= sbt_field_size[i] + hud_fontsize.x;
899 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
902 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
905 vector h_pos = item_pos;
906 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
908 bool complete = (this_team == NUM_SPECTATOR);
911 if((sbt_highlight) && (!(pl_number % 2)))
912 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
914 vector pos = item_pos;
915 pos.x += hud_fontsize.x * 0.5;
916 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
918 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
920 width_limit -= stringwidth("...", false, hud_fontsize);
921 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
922 float ping_padding = 0;
923 float min_pingsize = stringwidth("999", false, hud_fontsize);
924 for(i = 0; pl; pl = pl.sort_next)
926 if(pl.team != this_team)
932 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
933 if(this_team == NUM_SPECTATOR)
935 if(autocvar_hud_panel_scoreboard_spectators_showping)
937 string ping = Scoreboard_GetField(pl, SP_PING);
938 float pingsize = stringwidth(ping, false, hud_fontsize);
939 if(min_pingsize > pingsize)
940 ping_padding = min_pingsize - pingsize;
941 string col = rgb_to_hexcolor(sbt_field_rgb);
942 str = sprintf("%s ^7[%s%s^7]", str, col, ping);
945 else if(autocvar_hud_panel_scoreboard_others_showscore)
946 str = sprintf("%s ^7(^3%s^7)", str, ftos(pl.(scores(ps_primary))));
947 float str_width = stringwidth(str, true, hud_fontsize);
948 if(pos.x + str_width > width_limit)
953 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
958 pos.x = item_pos.x + hud_fontsize.x * 0.5;
959 pos.y = item_pos.y + i * (hud_fontsize.y * 1.25);
962 drawcolorcodedstring(pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
963 pos.x += str_width + hud_fontsize.x * 0.5;
964 pos.x += ping_padding;
966 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
969 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
971 int max_players = 999;
972 if(autocvar_hud_panel_scoreboard_maxheight > 0)
974 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
976 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
977 max_players = floor(max_players / (hud_fontsize.y * 1.25));
980 if(max_players == tm.team_size)
985 entity me = playerslots[current_player];
987 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
988 panel_size.y += panel_bg_padding * 2;
991 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
992 if(panel.current_panel_bg != "0")
993 end_pos.y += panel_bg_border * 2;
997 panel_pos += '1 1 0' * panel_bg_padding;
998 panel_size -= '2 2 0' * panel_bg_padding;
1002 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1006 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1008 pos.y += 1.25 * hud_fontsize.y;
1011 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1013 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1016 // print header row and highlight columns
1017 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1019 // fill the table and draw the rows
1020 bool is_self = false;
1021 bool self_shown = false;
1023 for(pl = players.sort_next; pl; pl = pl.sort_next)
1025 if(pl.team != tm.team)
1027 if(i == max_players - 2 && pl != me)
1029 if(!self_shown && me.team == tm.team)
1031 Scoreboard_DrawItem(pos, rgb, me, true, i);
1033 pos.y += 1.25 * hud_fontsize.y;
1037 if(i >= max_players - 1)
1039 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1042 is_self = (pl.sv_entnum == current_player);
1043 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1046 pos.y += 1.25 * hud_fontsize.y;
1050 panel_size.x += panel_bg_padding * 2; // restore initial width
1054 bool Scoreboard_WouldDraw()
1056 if (QuickMenu_IsOpened())
1058 else if (HUD_Radar_Clickable())
1060 else if (scoreboard_showscores)
1062 else if (intermission == 1)
1064 else if (intermission == 2)
1066 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1068 else if (scoreboard_showscores_force)
1073 float average_accuracy;
1074 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1076 WepSet weapons_stat = WepSet_GetFromStat();
1077 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1078 int disownedcnt = 0;
1080 FOREACH(Weapons, it != WEP_Null, {
1081 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1083 WepSet set = it.m_wepset;
1084 if (weapon_stats < 0)
1086 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1088 else if (!(weapons_stat & set || weapons_inmap & set))
1093 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1094 if (weapon_cnt <= 0) return pos;
1097 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1099 int columnns = ceil(weapon_cnt / rows);
1101 float weapon_height = 29;
1102 float height = hud_fontsize.y + weapon_height;
1104 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1105 pos.y += 1.25 * hud_fontsize.y;
1106 if(panel.current_panel_bg != "0")
1107 pos.y += panel_bg_border;
1110 panel_size.y = height * rows;
1111 panel_size.y += panel_bg_padding * 2;
1114 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1115 if(panel.current_panel_bg != "0")
1116 end_pos.y += panel_bg_border * 2;
1118 if(panel_bg_padding)
1120 panel_pos += '1 1 0' * panel_bg_padding;
1121 panel_size -= '2 2 0' * panel_bg_padding;
1125 vector tmp = panel_size;
1127 float weapon_width = tmp.x / columnns / rows;
1130 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1134 // column highlighting
1135 for (int i = 0; i < columnns; ++i)
1137 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1140 for (int i = 0; i < rows; ++i)
1141 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1144 average_accuracy = 0;
1145 int weapons_with_stats = 0;
1147 pos.x += weapon_width / 2;
1149 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1152 Accuracy_LoadColors();
1154 float oldposx = pos.x;
1158 FOREACH(Weapons, it != WEP_Null, {
1159 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1161 WepSet set = it.m_wepset;
1162 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1166 if (weapon_stats >= 0)
1167 weapon_alpha = sbt_fg_alpha;
1169 weapon_alpha = 0.2 * sbt_fg_alpha;
1172 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1174 if (weapon_stats >= 0) {
1175 weapons_with_stats += 1;
1176 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1179 s = sprintf("%d%%", weapon_stats * 100);
1182 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1184 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1185 rgb = Accuracy_GetColor(weapon_stats);
1187 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1189 tmpos.x += weapon_width * rows;
1190 pos.x += weapon_width * rows;
1191 if (rows == 2 && column == columnns - 1) {
1199 if (weapons_with_stats)
1200 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1202 panel_size.x += panel_bg_padding * 2; // restore initial width
1206 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1208 pos.x += hud_fontsize.x * 0.25;
1209 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1210 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1211 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1213 pos.y += hud_fontsize.y;
1218 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1219 float stat_secrets_found, stat_secrets_total;
1220 float stat_monsters_killed, stat_monsters_total;
1224 // get monster stats
1225 stat_monsters_killed = STAT(MONSTERS_KILLED);
1226 stat_monsters_total = STAT(MONSTERS_TOTAL);
1228 // get secrets stats
1229 stat_secrets_found = STAT(SECRETS_FOUND);
1230 stat_secrets_total = STAT(SECRETS_TOTAL);
1232 // get number of rows
1233 if(stat_secrets_total)
1235 if(stat_monsters_total)
1238 // if no rows, return
1242 // draw table header
1243 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1244 pos.y += 1.25 * hud_fontsize.y;
1245 if(panel.current_panel_bg != "0")
1246 pos.y += panel_bg_border;
1249 panel_size.y = hud_fontsize.y * rows;
1250 panel_size.y += panel_bg_padding * 2;
1253 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1254 if(panel.current_panel_bg != "0")
1255 end_pos.y += panel_bg_border * 2;
1257 if(panel_bg_padding)
1259 panel_pos += '1 1 0' * panel_bg_padding;
1260 panel_size -= '2 2 0' * panel_bg_padding;
1264 vector tmp = panel_size;
1267 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1270 if(stat_monsters_total)
1272 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1273 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1277 if(stat_secrets_total)
1279 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1280 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1283 panel_size.x += panel_bg_padding * 2; // restore initial width
1288 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1291 RANKINGS_RECEIVED_CNT = 0;
1292 for (i=RANKINGS_CNT-1; i>=0; --i)
1294 ++RANKINGS_RECEIVED_CNT;
1296 if (RANKINGS_RECEIVED_CNT == 0)
1299 vector hl_rgb = rgb + '0.5 0.5 0.5';
1301 pos.y += hud_fontsize.y;
1302 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1303 pos.y += 1.25 * hud_fontsize.y;
1304 if(panel.current_panel_bg != "0")
1305 pos.y += panel_bg_border;
1310 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1312 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1317 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1319 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1323 float ranksize = 3 * hud_fontsize.x;
1324 float timesize = 5.5 * hud_fontsize.x;
1325 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1326 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1327 columns = min(columns, RANKINGS_RECEIVED_CNT);
1329 // expand name column to fill the entire row
1330 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1331 namesize += available_space;
1332 columnsize.x += available_space;
1334 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1335 panel_size.y += panel_bg_padding * 2;
1339 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1340 if(panel.current_panel_bg != "0")
1341 end_pos.y += panel_bg_border * 2;
1343 if(panel_bg_padding)
1345 panel_pos += '1 1 0' * panel_bg_padding;
1346 panel_size -= '2 2 0' * panel_bg_padding;
1352 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1354 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1356 int column = 0, j = 0;
1357 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1364 if(grecordholder[i] == entcs_GetName(player_localnum))
1365 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1366 else if(!((j + column) & 1) && sbt_highlight)
1367 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1369 str = count_ordinal(i+1);
1370 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1371 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1372 str = grecordholder[i];
1374 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1375 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1377 pos.y += 1.25 * hud_fontsize.y;
1379 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1383 pos.x += panel_size.x / columns;
1384 pos.y = panel_pos.y;
1388 panel_size.x += panel_bg_padding * 2; // restore initial width
1392 void Scoreboard_Draw()
1394 if(!autocvar__hud_configure)
1396 if(!hud_draw_maximized) return;
1398 // frametime checks allow to toggle the scoreboard even when the game is paused
1399 if(scoreboard_active) {
1400 if(hud_configure_menu_open == 1)
1401 scoreboard_fade_alpha = 1;
1402 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1403 if (scoreboard_fadeinspeed && frametime)
1404 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1406 scoreboard_fade_alpha = 1;
1407 if(hud_fontsize_str != autocvar_hud_fontsize)
1409 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1410 Scoreboard_initFieldSizes();
1411 if(hud_fontsize_str)
1412 strunzone(hud_fontsize_str);
1413 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1417 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1418 if (scoreboard_fadeoutspeed && frametime)
1419 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1421 scoreboard_fade_alpha = 0;
1424 if (!scoreboard_fade_alpha)
1428 scoreboard_fade_alpha = 0;
1430 if (autocvar_hud_panel_scoreboard_dynamichud)
1433 HUD_Scale_Disable();
1435 if(scoreboard_fade_alpha <= 0)
1437 panel_fade_alpha *= scoreboard_fade_alpha;
1438 HUD_Panel_LoadCvars();
1440 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1441 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1442 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1443 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1444 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1445 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1447 // don't overlap with con_notify
1448 if(!autocvar__hud_configure)
1449 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1451 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1452 float fixed_scoreboard_width = bound(vid_conwidth * 0.4, vid_conwidth - excess, vid_conwidth * 0.93);
1453 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1454 panel_size.x = fixed_scoreboard_width;
1456 Scoreboard_UpdatePlayerTeams();
1458 vector pos = panel_pos;
1463 vector sb_heading_fontsize;
1464 sb_heading_fontsize = hud_fontsize * 2;
1465 draw_beginBoldFont();
1466 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1469 pos.y += sb_heading_fontsize.y;
1470 if(panel.current_panel_bg != "0")
1471 pos.y += panel_bg_border;
1473 // Draw the scoreboard
1474 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1477 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1481 vector panel_bg_color_save = panel_bg_color;
1482 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1483 if(panel.current_panel_bg != "0")
1484 team_score_baseoffset.x -= panel_bg_border;
1485 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1487 if(tm.team == NUM_SPECTATOR)
1492 draw_beginBoldFont();
1493 vector rgb = Team_ColorRGB(tm.team);
1494 str = ftos(tm.(teamscores(ts_primary)));
1495 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1497 if(ts_primary != ts_secondary)
1499 str = ftos(tm.(teamscores(ts_secondary)));
1500 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);
1503 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1504 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1505 else if(panel_bg_color_team > 0)
1506 panel_bg_color = rgb * panel_bg_color_team;
1508 panel_bg_color = rgb;
1509 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1511 panel_bg_color = panel_bg_color_save;
1515 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1516 if(tm.team != NUM_SPECTATOR)
1518 // display it anyway
1519 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1522 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1523 if(race_speedaward) {
1524 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);
1525 pos.y += 1.25 * hud_fontsize.y;
1527 if(race_speedaward_alltimebest) {
1528 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);
1529 pos.y += 1.25 * hud_fontsize.y;
1531 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1533 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1534 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1536 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1539 for(pl = players.sort_next; pl; pl = pl.sort_next)
1541 if(pl.team == NUM_SPECTATOR)
1543 draw_beginBoldFont();
1544 drawstring(pos, _("Spectators"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1546 pos.y += 1.25 * hud_fontsize.y;
1548 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1549 pos.y += 1.25 * hud_fontsize.y;
1555 // Print info string
1557 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1558 tl = STAT(TIMELIMIT);
1559 fl = STAT(FRAGLIMIT);
1560 ll = STAT(LEADLIMIT);
1561 if(gametype == MAPINFO_TYPE_LMS)
1564 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1569 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1573 str = strcat(str, _(" or"));
1576 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1577 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1578 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1579 TranslateScoresLabel(teamscores_label(ts_primary))));
1583 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1584 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1585 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1586 TranslateScoresLabel(scores_label(ps_primary))));
1591 if(tl > 0 || fl > 0)
1592 str = strcat(str, _(" or"));
1595 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1596 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1597 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1598 TranslateScoresLabel(teamscores_label(ts_primary))));
1602 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1603 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1604 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1605 TranslateScoresLabel(scores_label(ps_primary))));
1610 pos.y += 1.2 * hud_fontsize.y;
1611 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1613 // print information about respawn status
1614 float respawn_time = STAT(RESPAWN_TIME);
1618 if(respawn_time < 0)
1620 // a negative number means we are awaiting respawn, time value is still the same
1621 respawn_time *= -1; // remove mark now that we checked it
1623 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1624 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1626 str = sprintf(_("^1Respawning in ^3%s^1..."),
1627 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1628 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1630 count_seconds(ceil(respawn_time - time))
1634 else if(time < respawn_time)
1636 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1637 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1638 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1640 count_seconds(ceil(respawn_time - time))
1644 else if(time >= respawn_time)
1645 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1647 pos.y += 1.2 * hud_fontsize.y;
1648 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1651 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;