1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/miscfunctions.qh>
6 #include "quickmenu.qh"
7 #include <common/ent_cs.qh>
8 #include <common/constants.qh>
9 #include <common/net_linked.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/scores.qh>
13 #include <common/stats.qh>
14 #include <common/teams.qh>
18 const int MAX_SBT_FIELDS = MAX_SCORE;
20 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
21 float sbt_field_size[MAX_SBT_FIELDS + 1];
22 string sbt_field_title[MAX_SBT_FIELDS + 1];
25 string autocvar_hud_fontsize;
26 string hud_fontsize_str;
31 float sbt_fg_alpha_self;
33 float sbt_highlight_alpha;
34 float sbt_highlight_alpha_self;
36 // provide basic panel cvars to old clients
37 // TODO remove them after a future release (0.8.2+)
38 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
39 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
40 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
41 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
42 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
43 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
44 noref string autocvar_hud_panel_scoreboard_bg_border = "";
45 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
47 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
48 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
49 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
50 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
51 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
52 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
54 bool autocvar_hud_panel_scoreboard_table_highlight = true;
55 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
57 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
58 float autocvar_hud_panel_scoreboard_namesize = 15;
60 bool autocvar_hud_panel_scoreboard_accuracy = true;
61 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
62 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
63 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
65 bool autocvar_hud_panel_scoreboard_dynamichud = false;
67 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
68 bool autocvar_hud_panel_scoreboard_others_showscore = true;
69 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
70 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
71 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 void drawstringright(vector, string, vector, vector, float, float);
75 void drawstringcenter(vector, string, vector, vector, float, float);
77 // wrapper to put all possible scores titles through gettext
78 string TranslateScoresLabel(string l)
82 case "bckills": return CTX(_("SCO^bckills"));
83 case "bctime": return CTX(_("SCO^bctime"));
84 case "caps": return CTX(_("SCO^caps"));
85 case "captime": return CTX(_("SCO^captime"));
86 case "deaths": return CTX(_("SCO^deaths"));
87 case "destroyed": return CTX(_("SCO^destroyed"));
88 case "dmg": return CTX(_("SCO^damage"));
89 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
90 case "drops": return CTX(_("SCO^drops"));
91 case "faults": return CTX(_("SCO^faults"));
92 case "fckills": return CTX(_("SCO^fckills"));
93 case "goals": return CTX(_("SCO^goals"));
94 case "kckills": return CTX(_("SCO^kckills"));
95 case "kdratio": return CTX(_("SCO^kdratio"));
96 case "kd": return CTX(_("SCO^k/d"));
97 case "kdr": return CTX(_("SCO^kdr"));
98 case "kills": return CTX(_("SCO^kills"));
99 case "teamkills": return CTX(_("SCO^teamkills"));
100 case "laps": return CTX(_("SCO^laps"));
101 case "lives": return CTX(_("SCO^lives"));
102 case "losses": return CTX(_("SCO^losses"));
103 case "name": return CTX(_("SCO^name"));
104 case "sum": return CTX(_("SCO^sum"));
105 case "nick": return CTX(_("SCO^nick"));
106 case "objectives": return CTX(_("SCO^objectives"));
107 case "pickups": return CTX(_("SCO^pickups"));
108 case "ping": return CTX(_("SCO^ping"));
109 case "pl": return CTX(_("SCO^pl"));
110 case "pushes": return CTX(_("SCO^pushes"));
111 case "rank": return CTX(_("SCO^rank"));
112 case "returns": return CTX(_("SCO^returns"));
113 case "revivals": return CTX(_("SCO^revivals"));
114 case "rounds": return CTX(_("SCO^rounds won"));
115 case "score": return CTX(_("SCO^score"));
116 case "suicides": return CTX(_("SCO^suicides"));
117 case "takes": return CTX(_("SCO^takes"));
118 case "ticks": return CTX(_("SCO^ticks"));
123 void Scoreboard_InitScores()
127 ps_primary = ps_secondary = NULL;
128 ts_primary = ts_secondary = -1;
129 FOREACH(Scores, true, {
130 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
131 if(f == SFL_SORT_PRIO_PRIMARY)
133 if(f == SFL_SORT_PRIO_SECONDARY)
136 if(ps_secondary == NULL)
137 ps_secondary = ps_primary;
139 for(i = 0; i < MAX_TEAMSCORE; ++i)
141 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
142 if(f == SFL_SORT_PRIO_PRIMARY)
144 if(f == SFL_SORT_PRIO_SECONDARY)
147 if(ts_secondary == -1)
148 ts_secondary = ts_primary;
150 Cmd_Scoreboard_SetFields(0);
153 float SetTeam(entity pl, float Team);
155 void Scoreboard_UpdatePlayerTeams()
159 for(pl = players.sort_next; pl; pl = pl.sort_next)
162 int Team = entcs_GetScoreTeam(pl.sv_entnum);
163 if(SetTeam(pl, Team))
166 Scoreboard_UpdatePlayerPos(pl);
170 pl = players.sort_next;
175 print(strcat("PNUM: ", ftos(num), "\n"));
180 int Scoreboard_CompareScore(int vl, int vr, int f)
182 TC(int, vl); TC(int, vr); TC(int, f);
183 if(f & SFL_ZERO_IS_WORST)
185 if(vl == 0 && vr != 0)
187 if(vl != 0 && vr == 0)
191 return IS_INCREASING(f);
193 return IS_DECREASING(f);
197 float Scoreboard_ComparePlayerScores(entity left, entity right)
200 vl = entcs_GetTeam(left.sv_entnum);
201 vr = entcs_GetTeam(right.sv_entnum);
213 if(vl == NUM_SPECTATOR)
215 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
217 if(!left.gotscores && right.gotscores)
222 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
226 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
230 FOREACH(Scores, true, {
231 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
232 if (r >= 0) return r;
235 if (left.sv_entnum < right.sv_entnum)
241 void Scoreboard_UpdatePlayerPos(entity player)
244 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
246 SORT_SWAP(player, ent);
248 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
250 SORT_SWAP(ent, player);
254 float Scoreboard_CompareTeamScores(entity left, entity right)
258 if(left.team == NUM_SPECTATOR)
260 if(right.team == NUM_SPECTATOR)
263 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
267 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
271 for(i = 0; i < MAX_TEAMSCORE; ++i)
273 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
278 if (left.team < right.team)
284 void Scoreboard_UpdateTeamPos(entity Team)
287 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
289 SORT_SWAP(Team, ent);
291 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
293 SORT_SWAP(ent, Team);
297 void Cmd_Scoreboard_Help()
299 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
300 LOG_INFO(_("^3|---------------------------------------------------------------|"));
301 LOG_INFO(_("Usage:"));
302 LOG_INFO(_("^2scoreboard_columns_set default"));
303 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
304 LOG_INFO(_("The following field names are recognized (case insensitive):"));
305 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
308 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player"));
309 LOG_INFO(_("^3ping^7 Ping time"));
310 LOG_INFO(_("^3pl^7 Packet loss"));
311 LOG_INFO(_("^3elo^7 Player ELO"));
312 LOG_INFO(_("^3fps^7 Player FPS"));
313 LOG_INFO(_("^3kills^7 Number of kills"));
314 LOG_INFO(_("^3deaths^7 Number of deaths"));
315 LOG_INFO(_("^3suicides^7 Number of suicides"));
316 LOG_INFO(_("^3frags^7 kills - suicides"));
317 LOG_INFO(_("^3teamkills^7 Number of teamkills"));
318 LOG_INFO(_("^3kd^7 The kill-death ratio"));
319 LOG_INFO(_("^3dmg^7 The total damage done"));
320 LOG_INFO(_("^3dmgtaken^7 The total damage taken"));
321 LOG_INFO(_("^3sum^7 frags - deaths"));
322 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured"));
323 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
324 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)"));
325 LOG_INFO(_("^3fckills^7 Number of flag carrier kills"));
326 LOG_INFO(_("^3returns^7 Number of flag returns"));
327 LOG_INFO(_("^3drops^7 Number of flag drops"));
328 LOG_INFO(_("^3lives^7 Number of lives (LMS)"));
329 LOG_INFO(_("^3rank^7 Player rank"));
330 LOG_INFO(_("^3pushes^7 Number of players pushed into void"));
331 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void"));
332 LOG_INFO(_("^3kckills^7 Number of keys carrier kills"));
333 LOG_INFO(_("^3losses^7 Number of times a key was lost"));
334 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)"));
335 LOG_INFO(_("^3time^7 Total time raced (race/cts)"));
336 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)"));
337 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)"));
338 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)"));
339 LOG_INFO(_("^3bckills^7 Number of ball carrier kills"));
340 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway"));
341 LOG_INFO(_("^3score^7 Total score"));
344 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
345 "of game types, then a slash, to make the field show up only in these\n"
346 "or in all but these game types. You can also specify 'all' as a\n"
347 "field to show all fields available for the current game mode.\n\n"));
349 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
350 "include/exclude ALL teams/noteams game modes.\n\n"));
352 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
353 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields"
354 "right of the vertical bar aligned to the right.\n"));
355 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\nother gamemodes except DM.\n"));
358 // NOTE: adding a gametype with ? to not warn for an optional field
359 // make sure it's excluded in a previous exclusive rule, if any
360 // otherwise the previous exclusive rule warns anyway
361 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
362 #define SCOREBOARD_DEFAULT_COLUMNS \
363 "ping pl fps name |" \
364 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
365 " -teams,lms/deaths +ft,tdm/deaths" \
367 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
368 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
369 " +tdm,ft,dom,ons,as/teamkills"\
370 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
371 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
372 " +lms/lives +lms/rank" \
373 " +kh/kckills +kh/losses +kh/caps" \
374 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
375 " +as/objectives +nb/faults +nb/goals" \
376 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
377 " +dom/ticks +dom/takes" \
378 " -lms,rc,cts,inv,nb/score"
380 void Cmd_Scoreboard_SetFields(int argc)
385 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
389 return; // do nothing, we don't know gametype and scores yet
391 // sbt_fields uses strunzone on the titles!
392 if(!sbt_field_title[0])
393 for(i = 0; i < MAX_SBT_FIELDS; ++i)
394 sbt_field_title[i] = strzone("(null)");
396 // TODO: re enable with gametype dependant cvars?
397 if(argc < 3) // no arguments provided
398 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
401 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
405 if(argv(2) == "default")
406 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
407 else if(argv(2) == "all")
410 s = "ping pl name |";
411 FOREACH(Scores, true, {
413 if(it != ps_secondary)
414 if(scores_label(it) != "")
415 s = strcat(s, " ", scores_label(it));
417 if(ps_secondary != ps_primary)
418 s = strcat(s, " ", scores_label(ps_secondary));
419 s = strcat(s, " ", scores_label(ps_primary));
420 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
427 hud_fontsize = HUD_GetFontsize("hud_fontsize");
429 for(i = 1; i < argc - 1; ++i)
435 if(substring(str, 0, 1) == "?")
438 str = substring(str, 1, strlen(str) - 1);
441 slash = strstrofs(str, "/", 0);
444 pattern = substring(str, 0, slash);
445 str = substring(str, slash + 1, strlen(str) - (slash + 1));
447 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
451 strunzone(sbt_field_title[sbt_num_fields]);
452 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
453 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
454 str = strtolower(str);
459 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
460 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
461 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
462 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
463 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
464 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
465 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
466 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
467 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
468 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
471 FOREACH(Scores, true, {
472 if (str == strtolower(scores_label(it))) {
474 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
484 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
488 sbt_field[sbt_num_fields] = j;
491 if(j == ps_secondary)
492 have_secondary = true;
497 if(sbt_num_fields >= MAX_SBT_FIELDS)
501 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
503 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
504 have_secondary = true;
505 if(ps_primary == ps_secondary)
506 have_secondary = true;
507 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
509 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
513 strunzone(sbt_field_title[sbt_num_fields]);
514 for(i = sbt_num_fields; i > 0; --i)
516 sbt_field_title[i] = sbt_field_title[i-1];
517 sbt_field_size[i] = sbt_field_size[i-1];
518 sbt_field[i] = sbt_field[i-1];
520 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
521 sbt_field[0] = SP_NAME;
523 LOG_INFO("fixed missing field 'name'");
527 strunzone(sbt_field_title[sbt_num_fields]);
528 for(i = sbt_num_fields; i > 1; --i)
530 sbt_field_title[i] = sbt_field_title[i-1];
531 sbt_field_size[i] = sbt_field_size[i-1];
532 sbt_field[i] = sbt_field[i-1];
534 sbt_field_title[1] = strzone("|");
535 sbt_field[1] = SP_SEPARATOR;
536 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
538 LOG_INFO("fixed missing field '|'");
541 else if(!have_separator)
543 strunzone(sbt_field_title[sbt_num_fields]);
544 sbt_field_title[sbt_num_fields] = strzone("|");
545 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
546 sbt_field[sbt_num_fields] = SP_SEPARATOR;
548 LOG_INFO("fixed missing field '|'");
552 strunzone(sbt_field_title[sbt_num_fields]);
553 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
554 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
555 sbt_field[sbt_num_fields] = ps_secondary;
557 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
561 strunzone(sbt_field_title[sbt_num_fields]);
562 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
563 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
564 sbt_field[sbt_num_fields] = ps_primary;
566 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
570 sbt_field[sbt_num_fields] = SP_END;
574 vector sbt_field_rgb;
575 string sbt_field_icon0;
576 string sbt_field_icon1;
577 string sbt_field_icon2;
578 vector sbt_field_icon0_rgb;
579 vector sbt_field_icon1_rgb;
580 vector sbt_field_icon2_rgb;
581 string Scoreboard_GetName(entity pl)
583 if(ready_waiting && pl.ready)
585 sbt_field_icon0 = "gfx/scoreboard/player_ready";
589 int f = entcs_GetClientColors(pl.sv_entnum);
591 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
592 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
593 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
594 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
595 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
598 return entcs_GetName(pl.sv_entnum);
600 string Scoreboard_GetField(entity pl, PlayerScoreField field)
602 float tmp, num, denom;
605 sbt_field_rgb = '1 1 1';
606 sbt_field_icon0 = "";
607 sbt_field_icon1 = "";
608 sbt_field_icon2 = "";
609 sbt_field_icon0_rgb = '1 1 1';
610 sbt_field_icon1_rgb = '1 1 1';
611 sbt_field_icon2_rgb = '1 1 1';
616 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
617 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
621 tmp = max(0, min(220, f-80)) / 220;
622 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
628 f = pl.ping_packetloss;
629 tmp = pl.ping_movementloss;
630 if(f == 0 && tmp == 0)
632 str = ftos(ceil(f * 100));
634 str = strcat(str, "~", ftos(ceil(tmp * 100)));
635 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
636 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
640 return Scoreboard_GetName(pl);
643 f = pl.(scores(SP_KILLS));
644 f -= pl.(scores(SP_SUICIDES));
648 num = pl.(scores(SP_KILLS));
649 denom = pl.(scores(SP_DEATHS));
652 sbt_field_rgb = '0 1 0';
653 str = sprintf("%d", num);
654 } else if(num <= 0) {
655 sbt_field_rgb = '1 0 0';
656 str = sprintf("%.1f", num/denom);
658 str = sprintf("%.1f", num/denom);
662 f = pl.(scores(SP_KILLS));
663 f -= pl.(scores(SP_DEATHS));
666 sbt_field_rgb = '0 1 0';
668 sbt_field_rgb = '1 1 1';
670 sbt_field_rgb = '1 0 0';
676 float elo = pl.(scores(SP_ELO));
678 case -1: return "...";
679 case -2: return _("N/A");
680 default: return ftos(elo);
686 float fps = pl.(scores(SP_FPS));
689 sbt_field_rgb = '1 1 1';
690 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
692 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
693 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
697 case SP_DMG: case SP_DMGTAKEN:
698 return sprintf("%.1f k", pl.(scores(field)) / 1000);
700 default: case SP_SCORE:
701 tmp = pl.(scores(field));
702 f = scores_flags(field);
703 if(field == ps_primary)
704 sbt_field_rgb = '1 1 0';
705 else if(field == ps_secondary)
706 sbt_field_rgb = '0 1 1';
708 sbt_field_rgb = '1 1 1';
709 return ScoreString(f, tmp);
714 float sbt_fixcolumnwidth_len;
715 float sbt_fixcolumnwidth_iconlen;
716 float sbt_fixcolumnwidth_marginlen;
718 string Scoreboard_FixColumnWidth(int i, string str)
724 sbt_fixcolumnwidth_iconlen = 0;
726 if(sbt_field_icon0 != "")
728 sz = draw_getimagesize(sbt_field_icon0);
730 if(sbt_fixcolumnwidth_iconlen < f)
731 sbt_fixcolumnwidth_iconlen = f;
734 if(sbt_field_icon1 != "")
736 sz = draw_getimagesize(sbt_field_icon1);
738 if(sbt_fixcolumnwidth_iconlen < f)
739 sbt_fixcolumnwidth_iconlen = f;
742 if(sbt_field_icon2 != "")
744 sz = draw_getimagesize(sbt_field_icon2);
746 if(sbt_fixcolumnwidth_iconlen < f)
747 sbt_fixcolumnwidth_iconlen = f;
750 if(sbt_fixcolumnwidth_iconlen != 0)
752 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
753 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
756 sbt_fixcolumnwidth_marginlen = 0;
758 if(sbt_field[i] == SP_NAME) // name gets all remaining space
761 float remaining_space = 0;
762 for(j = 0; j < sbt_num_fields; ++j)
764 if (sbt_field[i] != SP_SEPARATOR)
765 remaining_space += sbt_field_size[j] + hud_fontsize.x;
766 sbt_field_size[i] = panel_size.x - remaining_space;
768 if (sbt_fixcolumnwidth_iconlen != 0)
769 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
770 float namesize = panel_size.x - remaining_space;
771 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
772 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
774 max_namesize = vid_conwidth - remaining_space;
777 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
779 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
780 if(sbt_field_size[i] < f)
781 sbt_field_size[i] = f;
786 void Scoreboard_initFieldSizes()
788 for(int i = 0; i < sbt_num_fields; ++i)
790 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
791 Scoreboard_FixColumnWidth(i, "");
795 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
798 vector column_dim = eY * panel_size.y;
800 column_dim.y -= 1.25 * hud_fontsize.y;
801 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
802 pos.x += hud_fontsize.x * 0.5;
803 for(i = 0; i < sbt_num_fields; ++i)
805 if(sbt_field[i] == SP_SEPARATOR)
807 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
810 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
811 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
812 pos.x += column_dim.x;
814 if(sbt_field[i] == SP_SEPARATOR)
816 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
817 for(i = sbt_num_fields - 1; i > 0; --i)
819 if(sbt_field[i] == SP_SEPARATOR)
822 pos.x -= sbt_field_size[i];
827 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
828 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
831 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
832 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
833 pos.x -= hud_fontsize.x;
838 pos.y += 1.25 * hud_fontsize.y;
842 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
844 TC(bool, is_self); TC(int, pl_number);
846 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
848 vector h_pos = item_pos;
849 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
850 // alternated rows highlighting
852 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
853 else if((sbt_highlight) && (!(pl_number % 2)))
854 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
856 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
858 vector pos = item_pos;
859 pos.x += hud_fontsize.x * 0.5;
860 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
861 vector tmp = '0 0 0';
863 PlayerScoreField field;
864 for(i = 0; i < sbt_num_fields; ++i)
866 field = sbt_field[i];
867 if(field == SP_SEPARATOR)
870 if(is_spec && field != SP_NAME && field != SP_PING) {
871 pos.x += sbt_field_size[i] + hud_fontsize.x;
874 str = Scoreboard_GetField(pl, field);
875 str = Scoreboard_FixColumnWidth(i, str);
877 pos.x += sbt_field_size[i] + hud_fontsize.x;
879 if(field == SP_NAME) {
880 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
881 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
883 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
884 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
887 tmp.x = sbt_field_size[i] + hud_fontsize.x;
888 if(sbt_field_icon0 != "")
889 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
890 if(sbt_field_icon1 != "")
891 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
892 if(sbt_field_icon2 != "")
893 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
896 if(sbt_field[i] == SP_SEPARATOR)
898 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
899 for(i = sbt_num_fields-1; i > 0; --i)
901 field = sbt_field[i];
902 if(field == SP_SEPARATOR)
905 if(is_spec && field != SP_NAME && field != SP_PING) {
906 pos.x -= sbt_field_size[i] + hud_fontsize.x;
910 str = Scoreboard_GetField(pl, field);
911 str = Scoreboard_FixColumnWidth(i, str);
913 if(field == SP_NAME) {
914 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
915 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
917 tmp.x = sbt_fixcolumnwidth_len;
918 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
921 tmp.x = sbt_field_size[i];
922 if(sbt_field_icon0 != "")
923 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
924 if(sbt_field_icon1 != "")
925 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
926 if(sbt_field_icon2 != "")
927 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 pos.x -= sbt_field_size[i] + hud_fontsize.x;
933 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
936 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
939 vector h_pos = item_pos;
940 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
942 bool complete = (this_team == NUM_SPECTATOR);
945 if((sbt_highlight) && (!(pl_number % 2)))
946 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
948 vector pos = item_pos;
949 pos.x += hud_fontsize.x * 0.5;
950 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
952 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
954 width_limit -= stringwidth("...", false, hud_fontsize);
955 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
956 static float max_name_width = 0;
959 float min_fieldsize = 0;
960 float fieldpadding = hud_fontsize.x * 0.25;
961 if(this_team == NUM_SPECTATOR)
963 if(autocvar_hud_panel_scoreboard_spectators_showping)
964 min_fieldsize = stringwidth("999", false, hud_fontsize);
966 else if(autocvar_hud_panel_scoreboard_others_showscore)
967 min_fieldsize = stringwidth("99", false, hud_fontsize);
968 for(i = 0; pl; pl = pl.sort_next)
970 if(pl.team != this_team)
976 if(this_team == NUM_SPECTATOR)
978 if(autocvar_hud_panel_scoreboard_spectators_showping)
979 field = Scoreboard_GetField(pl, SP_PING);
981 else if(autocvar_hud_panel_scoreboard_others_showscore)
982 field = Scoreboard_GetField(pl, SP_SCORE);
984 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
985 float column_width = stringwidth(str, true, hud_fontsize);
986 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
988 if(column_width > max_name_width)
989 max_name_width = column_width;
990 column_width = max_name_width;
994 fieldsize = stringwidth(field, false, hud_fontsize);
995 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
998 if(pos.x + column_width > width_limit)
1003 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1008 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1009 pos.y += hud_fontsize.y * 1.25;
1013 vector name_pos = pos;
1014 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1015 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1016 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1019 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1020 h_size.y = hud_fontsize.y;
1021 vector field_pos = pos;
1022 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1023 field_pos.x += column_width - h_size.x;
1025 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1026 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1027 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1029 pos.x += column_width;
1030 pos.x += hud_fontsize.x;
1032 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1035 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1037 int max_players = 999;
1038 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1040 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1043 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1044 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1045 height /= team_count;
1048 height -= panel_bg_padding * 2; // - padding
1049 max_players = floor(height / (hud_fontsize.y * 1.25));
1050 if(max_players <= 1)
1052 if(max_players == tm.team_size)
1057 entity me = playerslots[current_player];
1059 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1060 panel_size.y += panel_bg_padding * 2;
1063 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1064 if(panel.current_panel_bg != "0")
1065 end_pos.y += panel_bg_border * 2;
1067 if(panel_bg_padding)
1069 panel_pos += '1 1 0' * panel_bg_padding;
1070 panel_size -= '2 2 0' * panel_bg_padding;
1074 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1078 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1080 pos.y += 1.25 * hud_fontsize.y;
1083 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1085 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1088 // print header row and highlight columns
1089 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1091 // fill the table and draw the rows
1092 bool is_self = false;
1093 bool self_shown = false;
1095 for(pl = players.sort_next; pl; pl = pl.sort_next)
1097 if(pl.team != tm.team)
1099 if(i == max_players - 2 && pl != me)
1101 if(!self_shown && me.team == tm.team)
1103 Scoreboard_DrawItem(pos, rgb, me, true, i);
1105 pos.y += 1.25 * hud_fontsize.y;
1109 if(i >= max_players - 1)
1111 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1114 is_self = (pl.sv_entnum == current_player);
1115 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1118 pos.y += 1.25 * hud_fontsize.y;
1122 panel_size.x += panel_bg_padding * 2; // restore initial width
1126 bool Scoreboard_WouldDraw()
1128 if (MUTATOR_CALLHOOK(DrawScoreboard))
1130 else if (QuickMenu_IsOpened())
1132 else if (HUD_Radar_Clickable())
1134 else if (scoreboard_showscores)
1136 else if (intermission == 1)
1138 else if (intermission == 2)
1140 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1142 else if (scoreboard_showscores_force)
1147 float average_accuracy;
1148 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1150 WepSet weapons_stat = WepSet_GetFromStat();
1151 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1152 int disownedcnt = 0;
1154 FOREACH(Weapons, it != WEP_Null, {
1155 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1157 WepSet set = it.m_wepset;
1158 if(it.spawnflags & WEP_TYPE_OTHER)
1163 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1165 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1172 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1173 if (weapon_cnt <= 0) return pos;
1176 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1178 int columnns = ceil(weapon_cnt / rows);
1180 float weapon_height = 29;
1181 float height = hud_fontsize.y + weapon_height;
1183 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1184 pos.y += 1.25 * hud_fontsize.y;
1185 if(panel.current_panel_bg != "0")
1186 pos.y += panel_bg_border;
1189 panel_size.y = height * rows;
1190 panel_size.y += panel_bg_padding * 2;
1193 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1194 if(panel.current_panel_bg != "0")
1195 end_pos.y += panel_bg_border * 2;
1197 if(panel_bg_padding)
1199 panel_pos += '1 1 0' * panel_bg_padding;
1200 panel_size -= '2 2 0' * panel_bg_padding;
1204 vector tmp = panel_size;
1206 float weapon_width = tmp.x / columnns / rows;
1209 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1213 // column highlighting
1214 for (int i = 0; i < columnns; ++i)
1216 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1219 for (int i = 0; i < rows; ++i)
1220 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1223 average_accuracy = 0;
1224 int weapons_with_stats = 0;
1226 pos.x += weapon_width / 2;
1228 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1231 Accuracy_LoadColors();
1233 float oldposx = pos.x;
1237 FOREACH(Weapons, it != WEP_Null, {
1238 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1240 WepSet set = it.m_wepset;
1241 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1243 if (it.spawnflags & WEP_TYPE_OTHER)
1247 if (weapon_stats >= 0)
1248 weapon_alpha = sbt_fg_alpha;
1250 weapon_alpha = 0.2 * sbt_fg_alpha;
1253 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1255 if (weapon_stats >= 0) {
1256 weapons_with_stats += 1;
1257 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1260 s = sprintf("%d%%", weapon_stats * 100);
1263 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1265 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1266 rgb = Accuracy_GetColor(weapon_stats);
1268 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1270 tmpos.x += weapon_width * rows;
1271 pos.x += weapon_width * rows;
1272 if (rows == 2 && column == columnns - 1) {
1280 if (weapons_with_stats)
1281 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1283 panel_size.x += panel_bg_padding * 2; // restore initial width
1287 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1289 pos.x += hud_fontsize.x * 0.25;
1290 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1291 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1292 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1294 pos.y += hud_fontsize.y;
1299 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1300 float stat_secrets_found, stat_secrets_total;
1301 float stat_monsters_killed, stat_monsters_total;
1305 // get monster stats
1306 stat_monsters_killed = STAT(MONSTERS_KILLED);
1307 stat_monsters_total = STAT(MONSTERS_TOTAL);
1309 // get secrets stats
1310 stat_secrets_found = STAT(SECRETS_FOUND);
1311 stat_secrets_total = STAT(SECRETS_TOTAL);
1313 // get number of rows
1314 if(stat_secrets_total)
1316 if(stat_monsters_total)
1319 // if no rows, return
1323 // draw table header
1324 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1325 pos.y += 1.25 * hud_fontsize.y;
1326 if(panel.current_panel_bg != "0")
1327 pos.y += panel_bg_border;
1330 panel_size.y = hud_fontsize.y * rows;
1331 panel_size.y += panel_bg_padding * 2;
1334 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1335 if(panel.current_panel_bg != "0")
1336 end_pos.y += panel_bg_border * 2;
1338 if(panel_bg_padding)
1340 panel_pos += '1 1 0' * panel_bg_padding;
1341 panel_size -= '2 2 0' * panel_bg_padding;
1345 vector tmp = panel_size;
1348 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1351 if(stat_monsters_total)
1353 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1354 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1358 if(stat_secrets_total)
1360 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1361 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1364 panel_size.x += panel_bg_padding * 2; // restore initial width
1369 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1372 RANKINGS_RECEIVED_CNT = 0;
1373 for (i=RANKINGS_CNT-1; i>=0; --i)
1375 ++RANKINGS_RECEIVED_CNT;
1377 if (RANKINGS_RECEIVED_CNT == 0)
1380 vector hl_rgb = rgb + '0.5 0.5 0.5';
1382 pos.y += hud_fontsize.y;
1383 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1384 pos.y += 1.25 * hud_fontsize.y;
1385 if(panel.current_panel_bg != "0")
1386 pos.y += panel_bg_border;
1391 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1393 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1398 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1400 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1404 float ranksize = 3 * hud_fontsize.x;
1405 float timesize = 5 * hud_fontsize.x;
1406 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1407 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1408 columns = min(columns, RANKINGS_RECEIVED_CNT);
1410 // expand name column to fill the entire row
1411 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1412 namesize += available_space;
1413 columnsize.x += available_space;
1415 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1416 panel_size.y += panel_bg_padding * 2;
1420 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1421 if(panel.current_panel_bg != "0")
1422 end_pos.y += panel_bg_border * 2;
1424 if(panel_bg_padding)
1426 panel_pos += '1 1 0' * panel_bg_padding;
1427 panel_size -= '2 2 0' * panel_bg_padding;
1433 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1435 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1437 int column = 0, j = 0;
1438 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1445 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1446 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1447 else if(!((j + column) & 1) && sbt_highlight)
1448 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1450 str = count_ordinal(i+1);
1451 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1453 str = ColorTranslateRGB(grecordholder[i]);
1455 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1456 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1458 pos.y += 1.25 * hud_fontsize.y;
1460 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1464 pos.x += panel_size.x / columns;
1465 pos.y = panel_pos.y;
1469 panel_size.x += panel_bg_padding * 2; // restore initial width
1473 void Scoreboard_Draw()
1475 if(!autocvar__hud_configure)
1477 if(!hud_draw_maximized) return;
1479 // frametime checks allow to toggle the scoreboard even when the game is paused
1480 if(scoreboard_active) {
1481 if(hud_configure_menu_open == 1)
1482 scoreboard_fade_alpha = 1;
1483 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1484 if (scoreboard_fadeinspeed && frametime)
1485 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1487 scoreboard_fade_alpha = 1;
1488 if(hud_fontsize_str != autocvar_hud_fontsize)
1490 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1491 Scoreboard_initFieldSizes();
1492 if(hud_fontsize_str)
1493 strunzone(hud_fontsize_str);
1494 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1498 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1499 if (scoreboard_fadeoutspeed && frametime)
1500 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1502 scoreboard_fade_alpha = 0;
1505 if (!scoreboard_fade_alpha)
1509 scoreboard_fade_alpha = 0;
1511 if (autocvar_hud_panel_scoreboard_dynamichud)
1514 HUD_Scale_Disable();
1516 if(scoreboard_fade_alpha <= 0)
1518 panel_fade_alpha *= scoreboard_fade_alpha;
1519 HUD_Panel_LoadCvars();
1521 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1522 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1523 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1524 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1525 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1526 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1528 // don't overlap with con_notify
1529 if(!autocvar__hud_configure)
1530 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1532 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1533 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1534 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1535 panel_size.x = fixed_scoreboard_width;
1537 Scoreboard_UpdatePlayerTeams();
1539 vector pos = panel_pos;
1545 vector sb_heading_fontsize;
1546 sb_heading_fontsize = hud_fontsize * 2;
1547 draw_beginBoldFont();
1548 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1551 pos.y += sb_heading_fontsize.y;
1552 if(panel.current_panel_bg != "0")
1553 pos.y += panel_bg_border;
1555 // Draw the scoreboard
1556 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1559 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1563 vector panel_bg_color_save = panel_bg_color;
1564 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1565 if(panel.current_panel_bg != "0")
1566 team_score_baseoffset.x -= panel_bg_border;
1567 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1569 if(tm.team == NUM_SPECTATOR)
1574 draw_beginBoldFont();
1575 vector rgb = Team_ColorRGB(tm.team);
1576 str = ftos(tm.(teamscores(ts_primary)));
1577 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1578 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1580 if(ts_primary != ts_secondary)
1582 str = ftos(tm.(teamscores(ts_secondary)));
1583 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1584 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1587 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1588 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1589 else if(panel_bg_color_team > 0)
1590 panel_bg_color = rgb * panel_bg_color_team;
1592 panel_bg_color = rgb;
1593 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1595 panel_bg_color = panel_bg_color_save;
1599 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1600 if(tm.team != NUM_SPECTATOR)
1602 // display it anyway
1603 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1606 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1608 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1609 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1611 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1612 if(race_speedaward) {
1613 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);
1614 pos.y += 1.25 * hud_fontsize.y;
1616 if(race_speedaward_alltimebest) {
1617 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);
1618 pos.y += 1.25 * hud_fontsize.y;
1620 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1623 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1626 for(pl = players.sort_next; pl; pl = pl.sort_next)
1628 if(pl.team == NUM_SPECTATOR)
1630 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1631 if(tm.team == NUM_SPECTATOR)
1633 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1634 draw_beginBoldFont();
1635 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1637 pos.y += 1.25 * hud_fontsize.y;
1639 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1640 pos.y += 1.25 * hud_fontsize.y;
1646 // Print info string
1648 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1649 tl = STAT(TIMELIMIT);
1650 fl = STAT(FRAGLIMIT);
1651 ll = STAT(LEADLIMIT);
1652 if(gametype == MAPINFO_TYPE_LMS)
1655 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1660 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1664 str = strcat(str, _(" or"));
1667 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1668 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1669 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1670 TranslateScoresLabel(teamscores_label(ts_primary))));
1674 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1675 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1676 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1677 TranslateScoresLabel(scores_label(ps_primary))));
1682 if(tl > 0 || fl > 0)
1683 str = strcat(str, _(" or"));
1686 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1687 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1688 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1689 TranslateScoresLabel(teamscores_label(ts_primary))));
1693 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1694 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1695 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1696 TranslateScoresLabel(scores_label(ps_primary))));
1701 pos.y += 1.2 * hud_fontsize.y;
1702 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1704 // print information about respawn status
1705 float respawn_time = STAT(RESPAWN_TIME);
1709 if(respawn_time < 0)
1711 // a negative number means we are awaiting respawn, time value is still the same
1712 respawn_time *= -1; // remove mark now that we checked it
1714 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1715 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1717 str = sprintf(_("^1Respawning in ^3%s^1..."),
1718 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1719 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1721 count_seconds(ceil(respawn_time - time))
1725 else if(time < respawn_time)
1727 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1728 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1729 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1731 count_seconds(ceil(respawn_time - time))
1735 else if(time >= respawn_time)
1736 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1738 pos.y += 1.2 * hud_fontsize.y;
1739 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1742 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;