1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/main.qh>
6 #include <client/miscfunctions.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 const int MAX_SBT_FIELDS = MAX_SCORE;
21 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
22 float sbt_field_size[MAX_SBT_FIELDS + 1];
23 string sbt_field_title[MAX_SBT_FIELDS + 1];
26 string autocvar_hud_fontsize;
27 string hud_fontsize_str;
32 float sbt_fg_alpha_self;
34 float sbt_highlight_alpha;
35 float sbt_highlight_alpha_self;
37 // provide basic panel cvars to old clients
38 // TODO remove them after a future release (0.8.2+)
39 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
40 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
41 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
42 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
43 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
44 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
45 noref string autocvar_hud_panel_scoreboard_bg_border = "";
46 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
48 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
49 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
50 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
51 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
52 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
54 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
55 bool autocvar_hud_panel_scoreboard_table_highlight = true;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
57 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
58 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
59 float autocvar_hud_panel_scoreboard_namesize = 15;
61 bool autocvar_hud_panel_scoreboard_accuracy = true;
62 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
63 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
64 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
66 bool autocvar_hud_panel_scoreboard_dynamichud = false;
68 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
69 bool autocvar_hud_panel_scoreboard_others_showscore = true;
70 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
71 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
72 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 // wrapper to put all possible scores titles through gettext
75 string TranslateScoresLabel(string l)
79 case "bckills": return CTX(_("SCO^bckills"));
80 case "bctime": return CTX(_("SCO^bctime"));
81 case "caps": return CTX(_("SCO^caps"));
82 case "captime": return CTX(_("SCO^captime"));
83 case "deaths": return CTX(_("SCO^deaths"));
84 case "destroyed": return CTX(_("SCO^destroyed"));
85 case "dmg": return CTX(_("SCO^damage"));
86 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
87 case "drops": return CTX(_("SCO^drops"));
88 case "faults": return CTX(_("SCO^faults"));
89 case "fckills": return CTX(_("SCO^fckills"));
90 case "goals": return CTX(_("SCO^goals"));
91 case "kckills": return CTX(_("SCO^kckills"));
92 case "kdratio": return CTX(_("SCO^kdratio"));
93 case "kd": return CTX(_("SCO^k/d"));
94 case "kdr": return CTX(_("SCO^kdr"));
95 case "kills": return CTX(_("SCO^kills"));
96 case "teamkills": return CTX(_("SCO^teamkills"));
97 case "laps": return CTX(_("SCO^laps"));
98 case "lives": return CTX(_("SCO^lives"));
99 case "losses": return CTX(_("SCO^losses"));
100 case "name": return CTX(_("SCO^name"));
101 case "sum": return CTX(_("SCO^sum"));
102 case "nick": return CTX(_("SCO^nick"));
103 case "objectives": return CTX(_("SCO^objectives"));
104 case "pickups": return CTX(_("SCO^pickups"));
105 case "ping": return CTX(_("SCO^ping"));
106 case "pl": return CTX(_("SCO^pl"));
107 case "pushes": return CTX(_("SCO^pushes"));
108 case "rank": return CTX(_("SCO^rank"));
109 case "returns": return CTX(_("SCO^returns"));
110 case "revivals": return CTX(_("SCO^revivals"));
111 case "rounds": return CTX(_("SCO^rounds won"));
112 case "score": return CTX(_("SCO^score"));
113 case "suicides": return CTX(_("SCO^suicides"));
114 case "takes": return CTX(_("SCO^takes"));
115 case "ticks": return CTX(_("SCO^ticks"));
120 void Scoreboard_InitScores()
124 ps_primary = ps_secondary = NULL;
125 ts_primary = ts_secondary = -1;
126 FOREACH(Scores, true, {
127 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
128 if(f == SFL_SORT_PRIO_PRIMARY)
130 if(f == SFL_SORT_PRIO_SECONDARY)
133 if(ps_secondary == NULL)
134 ps_secondary = ps_primary;
136 for(i = 0; i < MAX_TEAMSCORE; ++i)
138 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
139 if(f == SFL_SORT_PRIO_PRIMARY)
141 if(f == SFL_SORT_PRIO_SECONDARY)
144 if(ts_secondary == -1)
145 ts_secondary = ts_primary;
147 Cmd_Scoreboard_SetFields(0);
151 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 int Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
296 LOG_INFO(_("Usage:"));
297 LOG_INFO("^2scoreboard_columns_set default");
298 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
299 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
300 LOG_INFO(_("The following field names are recognized (case insensitive):"));
303 LOG_INFO(strcat("^3name^7 ", _("Name of a player")));
304 LOG_INFO(strcat("^3nick^7 ", _("Name of a player")));
305 LOG_INFO(strcat("^3ping^7 ", _("Ping time")));
306 LOG_INFO(strcat("^3pl^7 ", _("Packet loss")));
307 LOG_INFO(strcat("^3elo^7 ", _("Player ELO")));
308 LOG_INFO(strcat("^3fps^7 ", _("Player FPS")));
309 LOG_INFO(strcat("^3kills^7 ", _("Number of kills")));
310 LOG_INFO(strcat("^3deaths^7 ", _("Number of deaths")));
311 LOG_INFO(strcat("^3suicides^7 ", _("Number of suicides")));
312 LOG_INFO(strcat("^3frags^7 ", _("kills - suicides")));
313 LOG_INFO(strcat("^3teamkills^7 ", _("Number of teamkills")));
314 LOG_INFO(strcat("^3kd^7 ", _("The kill-death ratio")));
315 LOG_INFO(strcat("^3dmg^7 ", _("The total damage done")));
316 LOG_INFO(strcat("^3dmgtaken^7 ", _("The total damage taken")));
317 LOG_INFO(strcat("^3sum^7 ", _("kills - deaths")));
318 LOG_INFO(strcat("^3caps^7 ", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
319 LOG_INFO(strcat("^3pickups^7 ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
320 LOG_INFO(strcat("^3captime^7 ", _("Time of fastest cap (CTF)")));
321 LOG_INFO(strcat("^3fckills^7 ", _("Number of flag carrier kills")));
322 LOG_INFO(strcat("^3returns^7 ", _("Number of flag returns")));
323 LOG_INFO(strcat("^3drops^7 ", _("Number of flag drops")));
324 LOG_INFO(strcat("^3lives^7 ", _("Number of lives (LMS)")));
325 LOG_INFO(strcat("^3rank^7 ", _("Player rank")));
326 LOG_INFO(strcat("^3pushes^7 ", _("Number of players pushed into void")));
327 LOG_INFO(strcat("^3destroyed^7 ", _("Number of keys destroyed by pushing them into void")));
328 LOG_INFO(strcat("^3kckills^7 ", _("Number of keys carrier kills")));
329 LOG_INFO(strcat("^3losses^7 ", _("Number of times a key was lost")));
330 LOG_INFO(strcat("^3laps^7 ", _("Number of laps finished (race/cts)")));
331 LOG_INFO(strcat("^3time^7 ", _("Total time raced (race/cts)")));
332 LOG_INFO(strcat("^3fastest^7 ", _("Time of fastest lap (race/cts)")));
333 LOG_INFO(strcat("^3ticks^7 ", _("Number of ticks (DOM)")));
334 LOG_INFO(strcat("^3takes^7 ", _("Number of domination points taken (DOM)")));
335 LOG_INFO(strcat("^3bckills^7 ", _("Number of ball carrier kills")));
336 LOG_INFO(strcat("^3bctime^7 ", _("Total amount of time holding the ball in Keepaway")));
337 LOG_INFO(strcat("^3score^7 ", _("Total score")));
340 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
341 "of game types, then a slash, to make the field show up only in these\n"
342 "or in all but these game types. You can also specify 'all' as a\n"
343 "field to show all fields available for the current game mode."));
346 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
347 "include/exclude ALL teams/noteams game modes."));
350 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
351 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
352 "right of the vertical bar aligned to the right."));
353 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
354 "other gamemodes except DM."));
357 // NOTE: adding a gametype with ? to not warn for an optional field
358 // make sure it's excluded in a previous exclusive rule, if any
359 // otherwise the previous exclusive rule warns anyway
360 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
361 #define SCOREBOARD_DEFAULT_COLUMNS \
362 "ping pl fps name |" \
363 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
364 " -teams,lms/deaths +ft,tdm/deaths" \
366 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
367 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
368 " +tdm,ft,dom,ons,as/teamkills"\
369 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
370 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
371 " +lms/lives +lms/rank" \
372 " +kh/kckills +kh/losses +kh/caps" \
373 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
374 " +as/objectives +nb/faults +nb/goals" \
375 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
376 " +dom/ticks +dom/takes" \
377 " -lms,rc,cts,inv,nb/score"
379 void Cmd_Scoreboard_SetFields(int argc)
384 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
388 return; // do nothing, we don't know gametype and scores yet
390 // sbt_fields uses strunzone on the titles!
391 if(!sbt_field_title[0])
392 for(i = 0; i < MAX_SBT_FIELDS; ++i)
393 sbt_field_title[i] = strzone("(null)");
395 // TODO: re enable with gametype dependant cvars?
396 if(argc < 3) // no arguments provided
397 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
400 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
404 if(argv(2) == "default")
405 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
406 else if(argv(2) == "all")
408 string s = "ping pl name |"; // scores without a label
409 FOREACH(Scores, true, {
411 if(it != ps_secondary)
412 if(scores_label(it) != "")
413 s = strcat(s, " ", scores_label(it));
415 if(ps_secondary != ps_primary)
416 s = strcat(s, " ", scores_label(ps_secondary));
417 s = strcat(s, " ", scores_label(ps_primary));
418 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
425 hud_fontsize = HUD_GetFontsize("hud_fontsize");
427 for(i = 1; i < argc - 1; ++i)
430 bool nocomplain = false;
431 if(substring(str, 0, 1) == "?")
434 str = substring(str, 1, strlen(str) - 1);
437 slash = strstrofs(str, "/", 0);
440 pattern = substring(str, 0, slash);
441 str = substring(str, slash + 1, strlen(str) - (slash + 1));
443 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
447 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
448 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
449 str = strtolower(str);
454 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
455 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
456 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
457 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
458 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
459 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
460 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
461 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
462 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
463 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
466 FOREACH(Scores, true, {
467 if (str == strtolower(scores_label(it))) {
469 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
479 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
483 sbt_field[sbt_num_fields] = j;
486 if(j == ps_secondary)
487 have_secondary = true;
492 if(sbt_num_fields >= MAX_SBT_FIELDS)
496 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
498 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
499 have_secondary = true;
500 if(ps_primary == ps_secondary)
501 have_secondary = true;
502 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
504 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
508 strunzone(sbt_field_title[sbt_num_fields]);
509 for(i = sbt_num_fields; i > 0; --i)
511 sbt_field_title[i] = sbt_field_title[i-1];
512 sbt_field_size[i] = sbt_field_size[i-1];
513 sbt_field[i] = sbt_field[i-1];
515 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
516 sbt_field[0] = SP_NAME;
518 LOG_INFO("fixed missing field 'name'");
522 strunzone(sbt_field_title[sbt_num_fields]);
523 for(i = sbt_num_fields; i > 1; --i)
525 sbt_field_title[i] = sbt_field_title[i-1];
526 sbt_field_size[i] = sbt_field_size[i-1];
527 sbt_field[i] = sbt_field[i-1];
529 sbt_field_title[1] = strzone("|");
530 sbt_field[1] = SP_SEPARATOR;
531 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
533 LOG_INFO("fixed missing field '|'");
536 else if(!have_separator)
538 strcpy(sbt_field_title[sbt_num_fields], "|");
539 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
540 sbt_field[sbt_num_fields] = SP_SEPARATOR;
542 LOG_INFO("fixed missing field '|'");
546 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
547 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
548 sbt_field[sbt_num_fields] = ps_secondary;
550 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
554 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
555 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
556 sbt_field[sbt_num_fields] = ps_primary;
558 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
562 sbt_field[sbt_num_fields] = SP_END;
566 vector sbt_field_rgb;
567 string sbt_field_icon0;
568 string sbt_field_icon1;
569 string sbt_field_icon2;
570 vector sbt_field_icon0_rgb;
571 vector sbt_field_icon1_rgb;
572 vector sbt_field_icon2_rgb;
573 string Scoreboard_GetName(entity pl)
575 if(ready_waiting && pl.ready)
577 sbt_field_icon0 = "gfx/scoreboard/player_ready";
581 int f = entcs_GetClientColors(pl.sv_entnum);
583 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
584 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
585 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
586 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
587 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
590 return entcs_GetName(pl.sv_entnum);
592 string Scoreboard_GetField(entity pl, PlayerScoreField field)
594 float tmp, num, denom;
597 sbt_field_rgb = '1 1 1';
598 sbt_field_icon0 = "";
599 sbt_field_icon1 = "";
600 sbt_field_icon2 = "";
601 sbt_field_icon0_rgb = '1 1 1';
602 sbt_field_icon1_rgb = '1 1 1';
603 sbt_field_icon2_rgb = '1 1 1';
608 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
609 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
613 tmp = max(0, min(220, f-80)) / 220;
614 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
620 f = pl.ping_packetloss;
621 tmp = pl.ping_movementloss;
622 if(f == 0 && tmp == 0)
624 str = ftos(ceil(f * 100));
626 str = strcat(str, "~", ftos(ceil(tmp * 100)));
627 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
628 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
632 return Scoreboard_GetName(pl);
635 f = pl.(scores(SP_KILLS));
636 f -= pl.(scores(SP_SUICIDES));
640 num = pl.(scores(SP_KILLS));
641 denom = pl.(scores(SP_DEATHS));
644 sbt_field_rgb = '0 1 0';
645 str = sprintf("%d", num);
646 } else if(num <= 0) {
647 sbt_field_rgb = '1 0 0';
648 str = sprintf("%.1f", num/denom);
650 str = sprintf("%.1f", num/denom);
654 f = pl.(scores(SP_KILLS));
655 f -= pl.(scores(SP_DEATHS));
658 sbt_field_rgb = '0 1 0';
660 sbt_field_rgb = '1 1 1';
662 sbt_field_rgb = '1 0 0';
668 float elo = pl.(scores(SP_ELO));
670 case -1: return "...";
671 case -2: return _("N/A");
672 default: return ftos(elo);
678 float fps = pl.(scores(SP_FPS));
681 sbt_field_rgb = '1 1 1';
682 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
684 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
685 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
689 case SP_DMG: case SP_DMGTAKEN:
690 return sprintf("%.1f k", pl.(scores(field)) / 1000);
692 default: case SP_SCORE:
693 tmp = pl.(scores(field));
694 f = scores_flags(field);
695 if(field == ps_primary)
696 sbt_field_rgb = '1 1 0';
697 else if(field == ps_secondary)
698 sbt_field_rgb = '0 1 1';
700 sbt_field_rgb = '1 1 1';
701 return ScoreString(f, tmp);
706 float sbt_fixcolumnwidth_len;
707 float sbt_fixcolumnwidth_iconlen;
708 float sbt_fixcolumnwidth_marginlen;
710 string Scoreboard_FixColumnWidth(int i, string str)
716 sbt_fixcolumnwidth_iconlen = 0;
718 if(sbt_field_icon0 != "")
720 sz = draw_getimagesize(sbt_field_icon0);
722 if(sbt_fixcolumnwidth_iconlen < f)
723 sbt_fixcolumnwidth_iconlen = f;
726 if(sbt_field_icon1 != "")
728 sz = draw_getimagesize(sbt_field_icon1);
730 if(sbt_fixcolumnwidth_iconlen < f)
731 sbt_fixcolumnwidth_iconlen = f;
734 if(sbt_field_icon2 != "")
736 sz = draw_getimagesize(sbt_field_icon2);
738 if(sbt_fixcolumnwidth_iconlen < f)
739 sbt_fixcolumnwidth_iconlen = f;
742 if(sbt_fixcolumnwidth_iconlen != 0)
744 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
745 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
748 sbt_fixcolumnwidth_marginlen = 0;
750 if(sbt_field[i] == SP_NAME) // name gets all remaining space
753 float remaining_space = 0;
754 for(j = 0; j < sbt_num_fields; ++j)
756 if (sbt_field[i] != SP_SEPARATOR)
757 remaining_space += sbt_field_size[j] + hud_fontsize.x;
758 sbt_field_size[i] = panel_size.x - remaining_space;
760 if (sbt_fixcolumnwidth_iconlen != 0)
761 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
762 float namesize = panel_size.x - remaining_space;
763 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
764 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
766 max_namesize = vid_conwidth - remaining_space;
769 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
771 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
772 if(sbt_field_size[i] < f)
773 sbt_field_size[i] = f;
778 void Scoreboard_initFieldSizes()
780 for(int i = 0; i < sbt_num_fields; ++i)
782 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
783 Scoreboard_FixColumnWidth(i, "");
787 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
790 vector column_dim = eY * panel_size.y;
792 column_dim.y -= 1.25 * hud_fontsize.y;
793 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
794 pos.x += hud_fontsize.x * 0.5;
795 for(i = 0; i < sbt_num_fields; ++i)
797 if(sbt_field[i] == SP_SEPARATOR)
799 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
802 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
803 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
804 pos.x += column_dim.x;
806 if(sbt_field[i] == SP_SEPARATOR)
808 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
809 for(i = sbt_num_fields - 1; i > 0; --i)
811 if(sbt_field[i] == SP_SEPARATOR)
814 pos.x -= sbt_field_size[i];
819 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
820 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
823 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
824 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
825 pos.x -= hud_fontsize.x;
830 pos.y += 1.25 * hud_fontsize.y;
834 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
836 TC(bool, is_self); TC(int, pl_number);
838 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
840 vector h_pos = item_pos;
841 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
842 // alternated rows highlighting
844 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
845 else if((sbt_highlight) && (!(pl_number % 2)))
846 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
848 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
850 vector pos = item_pos;
851 pos.x += hud_fontsize.x * 0.5;
852 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
853 vector tmp = '0 0 0';
855 PlayerScoreField field;
856 for(i = 0; i < sbt_num_fields; ++i)
858 field = sbt_field[i];
859 if(field == SP_SEPARATOR)
862 if(is_spec && field != SP_NAME && field != SP_PING) {
863 pos.x += sbt_field_size[i] + hud_fontsize.x;
866 str = Scoreboard_GetField(pl, field);
867 str = Scoreboard_FixColumnWidth(i, str);
869 pos.x += sbt_field_size[i] + hud_fontsize.x;
871 if(field == SP_NAME) {
872 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
873 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
875 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
876 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
879 tmp.x = sbt_field_size[i] + hud_fontsize.x;
880 if(sbt_field_icon0 != "")
881 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
882 if(sbt_field_icon1 != "")
883 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
884 if(sbt_field_icon2 != "")
885 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
888 if(sbt_field[i] == SP_SEPARATOR)
890 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
891 for(i = sbt_num_fields-1; i > 0; --i)
893 field = sbt_field[i];
894 if(field == SP_SEPARATOR)
897 if(is_spec && field != SP_NAME && field != SP_PING) {
898 pos.x -= sbt_field_size[i] + hud_fontsize.x;
902 str = Scoreboard_GetField(pl, field);
903 str = Scoreboard_FixColumnWidth(i, str);
905 if(field == SP_NAME) {
906 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
907 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
909 tmp.x = sbt_fixcolumnwidth_len;
910 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
913 tmp.x = sbt_field_size[i];
914 if(sbt_field_icon0 != "")
915 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
916 if(sbt_field_icon1 != "")
917 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 if(sbt_field_icon2 != "")
919 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
920 pos.x -= sbt_field_size[i] + hud_fontsize.x;
925 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
928 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
931 vector h_pos = item_pos;
932 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
934 bool complete = (this_team == NUM_SPECTATOR);
937 if((sbt_highlight) && (!(pl_number % 2)))
938 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
940 vector pos = item_pos;
941 pos.x += hud_fontsize.x * 0.5;
942 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
944 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
946 width_limit -= stringwidth("...", false, hud_fontsize);
947 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
948 static float max_name_width = 0;
951 float min_fieldsize = 0;
952 float fieldpadding = hud_fontsize.x * 0.25;
953 if(this_team == NUM_SPECTATOR)
955 if(autocvar_hud_panel_scoreboard_spectators_showping)
956 min_fieldsize = stringwidth("999", false, hud_fontsize);
958 else if(autocvar_hud_panel_scoreboard_others_showscore)
959 min_fieldsize = stringwidth("99", false, hud_fontsize);
960 for(i = 0; pl; pl = pl.sort_next)
962 if(pl.team != this_team)
968 if(this_team == NUM_SPECTATOR)
970 if(autocvar_hud_panel_scoreboard_spectators_showping)
971 field = Scoreboard_GetField(pl, SP_PING);
973 else if(autocvar_hud_panel_scoreboard_others_showscore)
974 field = Scoreboard_GetField(pl, SP_SCORE);
976 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
977 float column_width = stringwidth(str, true, hud_fontsize);
978 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
980 if(column_width > max_name_width)
981 max_name_width = column_width;
982 column_width = max_name_width;
986 fieldsize = stringwidth(field, false, hud_fontsize);
987 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
990 if(pos.x + column_width > width_limit)
995 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1000 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1001 pos.y += hud_fontsize.y * 1.25;
1005 vector name_pos = pos;
1006 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1007 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1008 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1011 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1012 h_size.y = hud_fontsize.y;
1013 vector field_pos = pos;
1014 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1015 field_pos.x += column_width - h_size.x;
1017 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1018 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1019 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1021 pos.x += column_width;
1022 pos.x += hud_fontsize.x;
1024 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1027 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1029 int max_players = 999;
1030 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1032 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1035 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1036 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1037 height /= team_count;
1040 height -= panel_bg_padding * 2; // - padding
1041 max_players = floor(height / (hud_fontsize.y * 1.25));
1042 if(max_players <= 1)
1044 if(max_players == tm.team_size)
1049 entity me = playerslots[current_player];
1051 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1052 panel_size.y += panel_bg_padding * 2;
1055 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1056 if(panel.current_panel_bg != "0")
1057 end_pos.y += panel_bg_border * 2;
1059 if(panel_bg_padding)
1061 panel_pos += '1 1 0' * panel_bg_padding;
1062 panel_size -= '2 2 0' * panel_bg_padding;
1066 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1070 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1072 pos.y += 1.25 * hud_fontsize.y;
1075 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1077 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1080 // print header row and highlight columns
1081 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1083 // fill the table and draw the rows
1084 bool is_self = false;
1085 bool self_shown = false;
1087 for(pl = players.sort_next; pl; pl = pl.sort_next)
1089 if(pl.team != tm.team)
1091 if(i == max_players - 2 && pl != me)
1093 if(!self_shown && me.team == tm.team)
1095 Scoreboard_DrawItem(pos, rgb, me, true, i);
1097 pos.y += 1.25 * hud_fontsize.y;
1101 if(i >= max_players - 1)
1103 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1106 is_self = (pl.sv_entnum == current_player);
1107 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1110 pos.y += 1.25 * hud_fontsize.y;
1114 panel_size.x += panel_bg_padding * 2; // restore initial width
1118 bool Scoreboard_WouldDraw()
1120 if (MUTATOR_CALLHOOK(DrawScoreboard))
1122 else if (QuickMenu_IsOpened())
1124 else if (HUD_Radar_Clickable())
1126 else if (scoreboard_showscores)
1128 else if (intermission == 1)
1130 else if (intermission == 2)
1132 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1134 else if (scoreboard_showscores_force)
1139 float average_accuracy;
1140 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1142 WepSet weapons_stat = WepSet_GetFromStat();
1143 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1144 int disownedcnt = 0;
1146 FOREACH(Weapons, it != WEP_Null, {
1147 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1149 WepSet set = it.m_wepset;
1150 if(it.spawnflags & WEP_TYPE_OTHER)
1155 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1157 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1164 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1165 if (weapon_cnt <= 0) return pos;
1168 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1170 int columnns = ceil(weapon_cnt / rows);
1172 float weapon_height = 29;
1173 float height = hud_fontsize.y + weapon_height;
1175 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1176 pos.y += 1.25 * hud_fontsize.y;
1177 if(panel.current_panel_bg != "0")
1178 pos.y += panel_bg_border;
1181 panel_size.y = height * rows;
1182 panel_size.y += panel_bg_padding * 2;
1185 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1186 if(panel.current_panel_bg != "0")
1187 end_pos.y += panel_bg_border * 2;
1189 if(panel_bg_padding)
1191 panel_pos += '1 1 0' * panel_bg_padding;
1192 panel_size -= '2 2 0' * panel_bg_padding;
1196 vector tmp = panel_size;
1198 float weapon_width = tmp.x / columnns / rows;
1201 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1205 // column highlighting
1206 for (int i = 0; i < columnns; ++i)
1208 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1211 for (int i = 0; i < rows; ++i)
1212 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1215 average_accuracy = 0;
1216 int weapons_with_stats = 0;
1218 pos.x += weapon_width / 2;
1220 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1223 Accuracy_LoadColors();
1225 float oldposx = pos.x;
1229 FOREACH(Weapons, it != WEP_Null, {
1230 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1232 WepSet set = it.m_wepset;
1233 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1235 if (it.spawnflags & WEP_TYPE_OTHER)
1239 if (weapon_stats >= 0)
1240 weapon_alpha = sbt_fg_alpha;
1242 weapon_alpha = 0.2 * sbt_fg_alpha;
1245 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1247 if (weapon_stats >= 0) {
1248 weapons_with_stats += 1;
1249 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1252 s = sprintf("%d%%", weapon_stats * 100);
1255 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1257 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1258 rgb = Accuracy_GetColor(weapon_stats);
1260 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1262 tmpos.x += weapon_width * rows;
1263 pos.x += weapon_width * rows;
1264 if (rows == 2 && column == columnns - 1) {
1272 if (weapons_with_stats)
1273 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1275 panel_size.x += panel_bg_padding * 2; // restore initial width
1279 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1281 pos.x += hud_fontsize.x * 0.25;
1282 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1283 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1284 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1286 pos.y += hud_fontsize.y;
1291 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1292 float stat_secrets_found, stat_secrets_total;
1293 float stat_monsters_killed, stat_monsters_total;
1297 // get monster stats
1298 stat_monsters_killed = STAT(MONSTERS_KILLED);
1299 stat_monsters_total = STAT(MONSTERS_TOTAL);
1301 // get secrets stats
1302 stat_secrets_found = STAT(SECRETS_FOUND);
1303 stat_secrets_total = STAT(SECRETS_TOTAL);
1305 // get number of rows
1306 if(stat_secrets_total)
1308 if(stat_monsters_total)
1311 // if no rows, return
1315 // draw table header
1316 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1317 pos.y += 1.25 * hud_fontsize.y;
1318 if(panel.current_panel_bg != "0")
1319 pos.y += panel_bg_border;
1322 panel_size.y = hud_fontsize.y * rows;
1323 panel_size.y += panel_bg_padding * 2;
1326 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1327 if(panel.current_panel_bg != "0")
1328 end_pos.y += panel_bg_border * 2;
1330 if(panel_bg_padding)
1332 panel_pos += '1 1 0' * panel_bg_padding;
1333 panel_size -= '2 2 0' * panel_bg_padding;
1337 vector tmp = panel_size;
1340 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1343 if(stat_monsters_total)
1345 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1346 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1350 if(stat_secrets_total)
1352 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1353 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1356 panel_size.x += panel_bg_padding * 2; // restore initial width
1361 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1364 RANKINGS_RECEIVED_CNT = 0;
1365 for (i=RANKINGS_CNT-1; i>=0; --i)
1367 ++RANKINGS_RECEIVED_CNT;
1369 if (RANKINGS_RECEIVED_CNT == 0)
1372 vector hl_rgb = rgb + '0.5 0.5 0.5';
1374 pos.y += hud_fontsize.y;
1375 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1376 pos.y += 1.25 * hud_fontsize.y;
1377 if(panel.current_panel_bg != "0")
1378 pos.y += panel_bg_border;
1383 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1385 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1390 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1392 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1396 float ranksize = 3 * hud_fontsize.x;
1397 float timesize = 5 * hud_fontsize.x;
1398 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1399 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1400 columns = min(columns, RANKINGS_RECEIVED_CNT);
1402 // expand name column to fill the entire row
1403 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1404 namesize += available_space;
1405 columnsize.x += available_space;
1407 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1408 panel_size.y += panel_bg_padding * 2;
1412 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1413 if(panel.current_panel_bg != "0")
1414 end_pos.y += panel_bg_border * 2;
1416 if(panel_bg_padding)
1418 panel_pos += '1 1 0' * panel_bg_padding;
1419 panel_size -= '2 2 0' * panel_bg_padding;
1425 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1427 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1429 int column = 0, j = 0;
1430 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1437 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1438 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1439 else if(!((j + column) & 1) && sbt_highlight)
1440 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1442 str = count_ordinal(i+1);
1443 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1444 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1445 str = ColorTranslateRGB(grecordholder[i]);
1447 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1448 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1450 pos.y += 1.25 * hud_fontsize.y;
1452 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1456 pos.x += panel_size.x / columns;
1457 pos.y = panel_pos.y;
1461 panel_size.x += panel_bg_padding * 2; // restore initial width
1465 void Scoreboard_Draw()
1467 if(!autocvar__hud_configure)
1469 if(!hud_draw_maximized) return;
1471 // frametime checks allow to toggle the scoreboard even when the game is paused
1472 if(scoreboard_active) {
1473 if(hud_configure_menu_open == 1)
1474 scoreboard_fade_alpha = 1;
1475 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1476 if (scoreboard_fadeinspeed && frametime)
1477 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1479 scoreboard_fade_alpha = 1;
1480 if(hud_fontsize_str != autocvar_hud_fontsize)
1482 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1483 Scoreboard_initFieldSizes();
1484 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1488 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1489 if (scoreboard_fadeoutspeed && frametime)
1490 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1492 scoreboard_fade_alpha = 0;
1495 if (!scoreboard_fade_alpha)
1499 scoreboard_fade_alpha = 0;
1501 if (autocvar_hud_panel_scoreboard_dynamichud)
1504 HUD_Scale_Disable();
1506 if(scoreboard_fade_alpha <= 0)
1508 panel_fade_alpha *= scoreboard_fade_alpha;
1509 HUD_Panel_LoadCvars();
1511 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1512 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1513 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1514 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1515 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1516 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1518 // don't overlap with con_notify
1519 if(!autocvar__hud_configure)
1520 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1522 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1523 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1524 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1525 panel_size.x = fixed_scoreboard_width;
1527 Scoreboard_UpdatePlayerTeams();
1529 vector pos = panel_pos;
1535 vector sb_heading_fontsize;
1536 sb_heading_fontsize = hud_fontsize * 2;
1537 draw_beginBoldFont();
1538 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1541 pos.y += sb_heading_fontsize.y;
1542 if(panel.current_panel_bg != "0")
1543 pos.y += panel_bg_border;
1545 // Draw the scoreboard
1546 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1549 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1553 vector panel_bg_color_save = panel_bg_color;
1554 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1555 if(panel.current_panel_bg != "0")
1556 team_score_baseoffset.x -= panel_bg_border;
1557 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1559 if(tm.team == NUM_SPECTATOR)
1564 draw_beginBoldFont();
1565 vector rgb = Team_ColorRGB(tm.team);
1566 str = ftos(tm.(teamscores(ts_primary)));
1567 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1568 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1570 if(ts_primary != ts_secondary)
1572 str = ftos(tm.(teamscores(ts_secondary)));
1573 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1574 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1577 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1578 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1579 else if(panel_bg_color_team > 0)
1580 panel_bg_color = rgb * panel_bg_color_team;
1582 panel_bg_color = rgb;
1583 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1585 panel_bg_color = panel_bg_color_save;
1589 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1590 if(tm.team != NUM_SPECTATOR)
1592 // display it anyway
1593 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1596 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1598 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1599 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1601 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1602 if(race_speedaward) {
1603 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);
1604 pos.y += 1.25 * hud_fontsize.y;
1606 if(race_speedaward_alltimebest) {
1607 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);
1608 pos.y += 1.25 * hud_fontsize.y;
1610 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1613 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1616 for(pl = players.sort_next; pl; pl = pl.sort_next)
1618 if(pl.team == NUM_SPECTATOR)
1620 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1621 if(tm.team == NUM_SPECTATOR)
1623 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1624 draw_beginBoldFont();
1625 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1627 pos.y += 1.25 * hud_fontsize.y;
1629 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1630 pos.y += 1.25 * hud_fontsize.y;
1636 // Print info string
1638 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1639 tl = STAT(TIMELIMIT);
1640 fl = STAT(FRAGLIMIT);
1641 ll = STAT(LEADLIMIT);
1642 if(gametype == MAPINFO_TYPE_LMS)
1645 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1650 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1654 str = strcat(str, _(" or"));
1657 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1658 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1659 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1660 TranslateScoresLabel(teamscores_label(ts_primary))));
1664 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1665 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1666 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1667 TranslateScoresLabel(scores_label(ps_primary))));
1672 if(tl > 0 || fl > 0)
1673 str = strcat(str, _(" or"));
1676 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1677 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1678 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1679 TranslateScoresLabel(teamscores_label(ts_primary))));
1683 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1684 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1685 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1686 TranslateScoresLabel(scores_label(ps_primary))));
1691 pos.y += 1.2 * hud_fontsize.y;
1692 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1694 // print information about respawn status
1695 float respawn_time = STAT(RESPAWN_TIME);
1699 if(respawn_time < 0)
1701 // a negative number means we are awaiting respawn, time value is still the same
1702 respawn_time *= -1; // remove mark now that we checked it
1704 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1705 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1707 str = sprintf(_("^1Respawning in ^3%s^1..."),
1708 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1709 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1711 count_seconds(ceil(respawn_time - time))
1715 else if(time < respawn_time)
1717 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
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)
1726 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1728 pos.y += 1.2 * hud_fontsize.y;
1729 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1732 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;