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 ^3default");
298 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
299 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
300 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
301 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
302 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
303 LOG_INFO(_("The following field names are recognized (case insensitive):"));
306 LOG_INFO(strcat("^3name^7 ", _("Name of a player")));
307 LOG_INFO(strcat("^3nick^7 ", _("Name of a player")));
308 LOG_INFO(strcat("^3ping^7 ", _("Ping time")));
309 LOG_INFO(strcat("^3pl^7 ", _("Packet loss")));
310 LOG_INFO(strcat("^3elo^7 ", _("Player ELO")));
311 LOG_INFO(strcat("^3fps^7 ", _("Player FPS")));
312 LOG_INFO(strcat("^3kills^7 ", _("Number of kills")));
313 LOG_INFO(strcat("^3deaths^7 ", _("Number of deaths")));
314 LOG_INFO(strcat("^3suicides^7 ", _("Number of suicides")));
315 LOG_INFO(strcat("^3frags^7 ", _("kills - suicides")));
316 LOG_INFO(strcat("^3teamkills^7 ", _("Number of teamkills")));
317 LOG_INFO(strcat("^3kd^7 ", _("The kill-death ratio")));
318 LOG_INFO(strcat("^3dmg^7 ", _("The total damage done")));
319 LOG_INFO(strcat("^3dmgtaken^7 ", _("The total damage taken")));
320 LOG_INFO(strcat("^3sum^7 ", _("kills - deaths")));
321 LOG_INFO(strcat("^3caps^7 ", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
322 LOG_INFO(strcat("^3pickups^7 ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
323 LOG_INFO(strcat("^3captime^7 ", _("Time of fastest cap (CTF)")));
324 LOG_INFO(strcat("^3fckills^7 ", _("Number of flag carrier kills")));
325 LOG_INFO(strcat("^3returns^7 ", _("Number of flag returns")));
326 LOG_INFO(strcat("^3drops^7 ", _("Number of flag drops")));
327 LOG_INFO(strcat("^3lives^7 ", _("Number of lives (LMS)")));
328 LOG_INFO(strcat("^3rank^7 ", _("Player rank")));
329 LOG_INFO(strcat("^3pushes^7 ", _("Number of players pushed into void")));
330 LOG_INFO(strcat("^3destroyed^7 ", _("Number of keys destroyed by pushing them into void")));
331 LOG_INFO(strcat("^3kckills^7 ", _("Number of keys carrier kills")));
332 LOG_INFO(strcat("^3losses^7 ", _("Number of times a key was lost")));
333 LOG_INFO(strcat("^3laps^7 ", _("Number of laps finished (race/cts)")));
334 LOG_INFO(strcat("^3time^7 ", _("Total time raced (race/cts)")));
335 LOG_INFO(strcat("^3fastest^7 ", _("Time of fastest lap (race/cts)")));
336 LOG_INFO(strcat("^3ticks^7 ", _("Number of ticks (DOM)")));
337 LOG_INFO(strcat("^3takes^7 ", _("Number of domination points taken (DOM)")));
338 LOG_INFO(strcat("^3bckills^7 ", _("Number of ball carrier kills")));
339 LOG_INFO(strcat("^3bctime^7 ", _("Total amount of time holding the ball in Keepaway")));
340 LOG_INFO(strcat("^3score^7 ", _("Total score")));
343 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
344 "of game types, then a slash, to make the field show up only in these\n"
345 "or in all but these game types. You can also specify 'all' as a\n"
346 "field to show all fields available for the current game mode."));
349 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
350 "include/exclude ALL teams/noteams game modes."));
353 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
354 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
355 "right of the vertical bar aligned to the right."));
356 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
357 "other gamemodes except DM."));
360 // NOTE: adding a gametype with ? to not warn for an optional field
361 // make sure it's excluded in a previous exclusive rule, if any
362 // otherwise the previous exclusive rule warns anyway
363 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
364 #define SCOREBOARD_DEFAULT_COLUMNS \
365 "ping pl fps name |" \
366 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
367 " -teams,lms/deaths +ft,tdm/deaths" \
369 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
370 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
371 " +tdm,ft,dom,ons,as/teamkills"\
372 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
373 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
374 " +lms/lives +lms/rank" \
375 " +kh/kckills +kh/losses +kh/caps" \
376 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
377 " +as/objectives +nb/faults +nb/goals" \
378 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
379 " +dom/ticks +dom/takes" \
380 " -lms,rc,cts,inv,nb/score"
382 void Cmd_Scoreboard_SetFields(int argc)
387 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
391 return; // do nothing, we don't know gametype and scores yet
393 // sbt_fields uses strunzone on the titles!
394 if(!sbt_field_title[0])
395 for(i = 0; i < MAX_SBT_FIELDS; ++i)
396 sbt_field_title[i] = strzone("(null)");
398 // TODO: re enable with gametype dependant cvars?
399 if(argc < 3) // no arguments provided
400 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
403 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
407 if(argv(2) == "default" || argv(2) == "expand_default")
409 if(argv(2) == "expand_default")
410 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
411 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
413 else if(argv(2) == "all")
415 string s = "ping pl name |"; // scores without a label
416 FOREACH(Scores, true, {
418 if(it != ps_secondary)
419 if(scores_label(it) != "")
420 s = strcat(s, " ", scores_label(it));
422 if(ps_secondary != ps_primary)
423 s = strcat(s, " ", scores_label(ps_secondary));
424 s = strcat(s, " ", scores_label(ps_primary));
425 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
432 hud_fontsize = HUD_GetFontsize("hud_fontsize");
434 for(i = 1; i < argc - 1; ++i)
437 bool nocomplain = false;
438 if(substring(str, 0, 1) == "?")
441 str = substring(str, 1, strlen(str) - 1);
444 slash = strstrofs(str, "/", 0);
447 pattern = substring(str, 0, slash);
448 str = substring(str, slash + 1, strlen(str) - (slash + 1));
450 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
454 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
455 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
456 str = strtolower(str);
461 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
462 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
463 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
464 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
465 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
466 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
467 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
468 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
469 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
470 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
473 FOREACH(Scores, true, {
474 if (str == strtolower(scores_label(it))) {
476 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
486 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
490 sbt_field[sbt_num_fields] = j;
493 if(j == ps_secondary)
494 have_secondary = true;
499 if(sbt_num_fields >= MAX_SBT_FIELDS)
503 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
505 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
506 have_secondary = true;
507 if(ps_primary == ps_secondary)
508 have_secondary = true;
509 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
511 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
515 strunzone(sbt_field_title[sbt_num_fields]);
516 for(i = sbt_num_fields; i > 0; --i)
518 sbt_field_title[i] = sbt_field_title[i-1];
519 sbt_field_size[i] = sbt_field_size[i-1];
520 sbt_field[i] = sbt_field[i-1];
522 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
523 sbt_field[0] = SP_NAME;
525 LOG_INFO("fixed missing field 'name'");
529 strunzone(sbt_field_title[sbt_num_fields]);
530 for(i = sbt_num_fields; i > 1; --i)
532 sbt_field_title[i] = sbt_field_title[i-1];
533 sbt_field_size[i] = sbt_field_size[i-1];
534 sbt_field[i] = sbt_field[i-1];
536 sbt_field_title[1] = strzone("|");
537 sbt_field[1] = SP_SEPARATOR;
538 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
540 LOG_INFO("fixed missing field '|'");
543 else if(!have_separator)
545 strcpy(sbt_field_title[sbt_num_fields], "|");
546 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
547 sbt_field[sbt_num_fields] = SP_SEPARATOR;
549 LOG_INFO("fixed missing field '|'");
553 strcpy(sbt_field_title[sbt_num_fields], 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 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
562 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
563 sbt_field[sbt_num_fields] = ps_primary;
565 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
569 sbt_field[sbt_num_fields] = SP_END;
573 vector sbt_field_rgb;
574 string sbt_field_icon0;
575 string sbt_field_icon1;
576 string sbt_field_icon2;
577 vector sbt_field_icon0_rgb;
578 vector sbt_field_icon1_rgb;
579 vector sbt_field_icon2_rgb;
580 string Scoreboard_GetName(entity pl)
582 if(ready_waiting && pl.ready)
584 sbt_field_icon0 = "gfx/scoreboard/player_ready";
588 int f = entcs_GetClientColors(pl.sv_entnum);
590 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
591 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
592 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
593 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
594 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
597 return entcs_GetName(pl.sv_entnum);
599 string Scoreboard_GetField(entity pl, PlayerScoreField field)
601 float tmp, num, denom;
604 sbt_field_rgb = '1 1 1';
605 sbt_field_icon0 = "";
606 sbt_field_icon1 = "";
607 sbt_field_icon2 = "";
608 sbt_field_icon0_rgb = '1 1 1';
609 sbt_field_icon1_rgb = '1 1 1';
610 sbt_field_icon2_rgb = '1 1 1';
615 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
616 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
620 tmp = max(0, min(220, f-80)) / 220;
621 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
627 f = pl.ping_packetloss;
628 tmp = pl.ping_movementloss;
629 if(f == 0 && tmp == 0)
631 str = ftos(ceil(f * 100));
633 str = strcat(str, "~", ftos(ceil(tmp * 100)));
634 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
635 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
639 return Scoreboard_GetName(pl);
642 f = pl.(scores(SP_KILLS));
643 f -= pl.(scores(SP_SUICIDES));
647 num = pl.(scores(SP_KILLS));
648 denom = pl.(scores(SP_DEATHS));
651 sbt_field_rgb = '0 1 0';
652 str = sprintf("%d", num);
653 } else if(num <= 0) {
654 sbt_field_rgb = '1 0 0';
655 str = sprintf("%.1f", num/denom);
657 str = sprintf("%.1f", num/denom);
661 f = pl.(scores(SP_KILLS));
662 f -= pl.(scores(SP_DEATHS));
665 sbt_field_rgb = '0 1 0';
667 sbt_field_rgb = '1 1 1';
669 sbt_field_rgb = '1 0 0';
675 float elo = pl.(scores(SP_ELO));
677 case -1: return "...";
678 case -2: return _("N/A");
679 default: return ftos(elo);
685 float fps = pl.(scores(SP_FPS));
688 sbt_field_rgb = '1 1 1';
689 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
691 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
692 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
696 case SP_DMG: case SP_DMGTAKEN:
697 return sprintf("%.1f k", pl.(scores(field)) / 1000);
699 default: case SP_SCORE:
700 tmp = pl.(scores(field));
701 f = scores_flags(field);
702 if(field == ps_primary)
703 sbt_field_rgb = '1 1 0';
704 else if(field == ps_secondary)
705 sbt_field_rgb = '0 1 1';
707 sbt_field_rgb = '1 1 1';
708 return ScoreString(f, tmp);
713 float sbt_fixcolumnwidth_len;
714 float sbt_fixcolumnwidth_iconlen;
715 float sbt_fixcolumnwidth_marginlen;
717 string Scoreboard_FixColumnWidth(int i, string str)
723 sbt_fixcolumnwidth_iconlen = 0;
725 if(sbt_field_icon0 != "")
727 sz = draw_getimagesize(sbt_field_icon0);
729 if(sbt_fixcolumnwidth_iconlen < f)
730 sbt_fixcolumnwidth_iconlen = f;
733 if(sbt_field_icon1 != "")
735 sz = draw_getimagesize(sbt_field_icon1);
737 if(sbt_fixcolumnwidth_iconlen < f)
738 sbt_fixcolumnwidth_iconlen = f;
741 if(sbt_field_icon2 != "")
743 sz = draw_getimagesize(sbt_field_icon2);
745 if(sbt_fixcolumnwidth_iconlen < f)
746 sbt_fixcolumnwidth_iconlen = f;
749 if(sbt_fixcolumnwidth_iconlen != 0)
751 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
752 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
755 sbt_fixcolumnwidth_marginlen = 0;
757 if(sbt_field[i] == SP_NAME) // name gets all remaining space
760 float remaining_space = 0;
761 for(j = 0; j < sbt_num_fields; ++j)
763 if (sbt_field[i] != SP_SEPARATOR)
764 remaining_space += sbt_field_size[j] + hud_fontsize.x;
765 sbt_field_size[i] = panel_size.x - remaining_space;
767 if (sbt_fixcolumnwidth_iconlen != 0)
768 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
769 float namesize = panel_size.x - remaining_space;
770 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
771 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
773 max_namesize = vid_conwidth - remaining_space;
776 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
778 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
779 if(sbt_field_size[i] < f)
780 sbt_field_size[i] = f;
785 void Scoreboard_initFieldSizes()
787 for(int i = 0; i < sbt_num_fields; ++i)
789 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
790 Scoreboard_FixColumnWidth(i, "");
794 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
797 vector column_dim = eY * panel_size.y;
799 column_dim.y -= 1.25 * hud_fontsize.y;
800 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
801 pos.x += hud_fontsize.x * 0.5;
802 for(i = 0; i < sbt_num_fields; ++i)
804 if(sbt_field[i] == SP_SEPARATOR)
806 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
809 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
810 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
811 pos.x += column_dim.x;
813 if(sbt_field[i] == SP_SEPARATOR)
815 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
816 for(i = sbt_num_fields - 1; i > 0; --i)
818 if(sbt_field[i] == SP_SEPARATOR)
821 pos.x -= sbt_field_size[i];
826 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
827 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
830 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
831 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
832 pos.x -= hud_fontsize.x;
837 pos.y += 1.25 * hud_fontsize.y;
841 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
843 TC(bool, is_self); TC(int, pl_number);
845 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
847 vector h_pos = item_pos;
848 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
849 // alternated rows highlighting
851 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
852 else if((sbt_highlight) && (!(pl_number % 2)))
853 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
855 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
857 vector pos = item_pos;
858 pos.x += hud_fontsize.x * 0.5;
859 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
860 vector tmp = '0 0 0';
862 PlayerScoreField field;
863 for(i = 0; i < sbt_num_fields; ++i)
865 field = sbt_field[i];
866 if(field == SP_SEPARATOR)
869 if(is_spec && field != SP_NAME && field != SP_PING) {
870 pos.x += sbt_field_size[i] + hud_fontsize.x;
873 str = Scoreboard_GetField(pl, field);
874 str = Scoreboard_FixColumnWidth(i, str);
876 pos.x += sbt_field_size[i] + hud_fontsize.x;
878 if(field == SP_NAME) {
879 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
880 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
882 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
883 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
886 tmp.x = sbt_field_size[i] + hud_fontsize.x;
887 if(sbt_field_icon0 != "")
888 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
889 if(sbt_field_icon1 != "")
890 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
891 if(sbt_field_icon2 != "")
892 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
895 if(sbt_field[i] == SP_SEPARATOR)
897 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
898 for(i = sbt_num_fields-1; i > 0; --i)
900 field = sbt_field[i];
901 if(field == SP_SEPARATOR)
904 if(is_spec && field != SP_NAME && field != SP_PING) {
905 pos.x -= sbt_field_size[i] + hud_fontsize.x;
909 str = Scoreboard_GetField(pl, field);
910 str = Scoreboard_FixColumnWidth(i, str);
912 if(field == SP_NAME) {
913 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
914 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
916 tmp.x = sbt_fixcolumnwidth_len;
917 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
920 tmp.x = sbt_field_size[i];
921 if(sbt_field_icon0 != "")
922 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
923 if(sbt_field_icon1 != "")
924 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
925 if(sbt_field_icon2 != "")
926 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
927 pos.x -= sbt_field_size[i] + hud_fontsize.x;
932 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
935 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
938 vector h_pos = item_pos;
939 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
941 bool complete = (this_team == NUM_SPECTATOR);
944 if((sbt_highlight) && (!(pl_number % 2)))
945 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
947 vector pos = item_pos;
948 pos.x += hud_fontsize.x * 0.5;
949 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
951 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
953 width_limit -= stringwidth("...", false, hud_fontsize);
954 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
955 static float max_name_width = 0;
958 float min_fieldsize = 0;
959 float fieldpadding = hud_fontsize.x * 0.25;
960 if(this_team == NUM_SPECTATOR)
962 if(autocvar_hud_panel_scoreboard_spectators_showping)
963 min_fieldsize = stringwidth("999", false, hud_fontsize);
965 else if(autocvar_hud_panel_scoreboard_others_showscore)
966 min_fieldsize = stringwidth("99", false, hud_fontsize);
967 for(i = 0; pl; pl = pl.sort_next)
969 if(pl.team != this_team)
975 if(this_team == NUM_SPECTATOR)
977 if(autocvar_hud_panel_scoreboard_spectators_showping)
978 field = Scoreboard_GetField(pl, SP_PING);
980 else if(autocvar_hud_panel_scoreboard_others_showscore)
981 field = Scoreboard_GetField(pl, SP_SCORE);
983 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
984 float column_width = stringwidth(str, true, hud_fontsize);
985 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
987 if(column_width > max_name_width)
988 max_name_width = column_width;
989 column_width = max_name_width;
993 fieldsize = stringwidth(field, false, hud_fontsize);
994 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
997 if(pos.x + column_width > width_limit)
1002 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1007 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1008 pos.y += hud_fontsize.y * 1.25;
1012 vector name_pos = pos;
1013 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1014 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1015 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1018 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1019 h_size.y = hud_fontsize.y;
1020 vector field_pos = pos;
1021 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1022 field_pos.x += column_width - h_size.x;
1024 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1025 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1026 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1030 h_size.x = column_width + hud_fontsize.x * 0.25;
1031 h_size.y = hud_fontsize.y;
1032 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1034 pos.x += column_width;
1035 pos.x += hud_fontsize.x;
1037 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1040 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1042 int max_players = 999;
1043 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1045 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1048 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1049 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1050 height /= team_count;
1053 height -= panel_bg_padding * 2; // - padding
1054 max_players = floor(height / (hud_fontsize.y * 1.25));
1055 if(max_players <= 1)
1057 if(max_players == tm.team_size)
1062 entity me = playerslots[current_player];
1064 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1065 panel_size.y += panel_bg_padding * 2;
1068 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1069 if(panel.current_panel_bg != "0")
1070 end_pos.y += panel_bg_border * 2;
1072 if(panel_bg_padding)
1074 panel_pos += '1 1 0' * panel_bg_padding;
1075 panel_size -= '2 2 0' * panel_bg_padding;
1079 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1083 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1085 pos.y += 1.25 * hud_fontsize.y;
1088 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1090 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1093 // print header row and highlight columns
1094 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1096 // fill the table and draw the rows
1097 bool is_self = false;
1098 bool self_shown = false;
1100 for(pl = players.sort_next; pl; pl = pl.sort_next)
1102 if(pl.team != tm.team)
1104 if(i == max_players - 2 && pl != me)
1106 if(!self_shown && me.team == tm.team)
1108 Scoreboard_DrawItem(pos, rgb, me, true, i);
1110 pos.y += 1.25 * hud_fontsize.y;
1114 if(i >= max_players - 1)
1116 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1119 is_self = (pl.sv_entnum == current_player);
1120 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1123 pos.y += 1.25 * hud_fontsize.y;
1127 panel_size.x += panel_bg_padding * 2; // restore initial width
1131 bool Scoreboard_WouldDraw()
1133 if (MUTATOR_CALLHOOK(DrawScoreboard))
1135 else if (QuickMenu_IsOpened())
1137 else if (HUD_Radar_Clickable())
1139 else if (scoreboard_showscores)
1141 else if (intermission == 1)
1143 else if (intermission == 2)
1145 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1147 else if (scoreboard_showscores_force)
1152 float average_accuracy;
1153 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1155 WepSet weapons_stat = WepSet_GetFromStat();
1156 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1157 int disownedcnt = 0;
1159 FOREACH(Weapons, it != WEP_Null, {
1160 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1162 WepSet set = it.m_wepset;
1163 if(it.spawnflags & WEP_TYPE_OTHER)
1168 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1170 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1177 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1178 if (weapon_cnt <= 0) return pos;
1181 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1183 int columnns = ceil(weapon_cnt / rows);
1185 float weapon_height = 29;
1186 float height = hud_fontsize.y + weapon_height;
1188 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1189 pos.y += 1.25 * hud_fontsize.y;
1190 if(panel.current_panel_bg != "0")
1191 pos.y += panel_bg_border;
1194 panel_size.y = height * rows;
1195 panel_size.y += panel_bg_padding * 2;
1198 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1199 if(panel.current_panel_bg != "0")
1200 end_pos.y += panel_bg_border * 2;
1202 if(panel_bg_padding)
1204 panel_pos += '1 1 0' * panel_bg_padding;
1205 panel_size -= '2 2 0' * panel_bg_padding;
1209 vector tmp = panel_size;
1211 float weapon_width = tmp.x / columnns / rows;
1214 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1218 // column highlighting
1219 for (int i = 0; i < columnns; ++i)
1221 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1224 for (int i = 0; i < rows; ++i)
1225 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1228 average_accuracy = 0;
1229 int weapons_with_stats = 0;
1231 pos.x += weapon_width / 2;
1233 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1236 Accuracy_LoadColors();
1238 float oldposx = pos.x;
1242 FOREACH(Weapons, it != WEP_Null, {
1243 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1245 WepSet set = it.m_wepset;
1246 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1248 if (it.spawnflags & WEP_TYPE_OTHER)
1252 if (weapon_stats >= 0)
1253 weapon_alpha = sbt_fg_alpha;
1255 weapon_alpha = 0.2 * sbt_fg_alpha;
1258 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1260 if (weapon_stats >= 0) {
1261 weapons_with_stats += 1;
1262 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1265 s = sprintf("%d%%", weapon_stats * 100);
1268 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1270 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1271 rgb = Accuracy_GetColor(weapon_stats);
1273 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1275 tmpos.x += weapon_width * rows;
1276 pos.x += weapon_width * rows;
1277 if (rows == 2 && column == columnns - 1) {
1285 if (weapons_with_stats)
1286 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1288 panel_size.x += panel_bg_padding * 2; // restore initial width
1292 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1294 pos.x += hud_fontsize.x * 0.25;
1295 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1296 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1297 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1299 pos.y += hud_fontsize.y;
1304 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1305 float stat_secrets_found, stat_secrets_total;
1306 float stat_monsters_killed, stat_monsters_total;
1310 // get monster stats
1311 stat_monsters_killed = STAT(MONSTERS_KILLED);
1312 stat_monsters_total = STAT(MONSTERS_TOTAL);
1314 // get secrets stats
1315 stat_secrets_found = STAT(SECRETS_FOUND);
1316 stat_secrets_total = STAT(SECRETS_TOTAL);
1318 // get number of rows
1319 if(stat_secrets_total)
1321 if(stat_monsters_total)
1324 // if no rows, return
1328 // draw table header
1329 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1330 pos.y += 1.25 * hud_fontsize.y;
1331 if(panel.current_panel_bg != "0")
1332 pos.y += panel_bg_border;
1335 panel_size.y = hud_fontsize.y * rows;
1336 panel_size.y += panel_bg_padding * 2;
1339 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1340 if(panel.current_panel_bg != "0")
1341 end_pos.y += panel_bg_border * 2;
1343 if(panel_bg_padding)
1345 panel_pos += '1 1 0' * panel_bg_padding;
1346 panel_size -= '2 2 0' * panel_bg_padding;
1350 vector tmp = panel_size;
1353 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1356 if(stat_monsters_total)
1358 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1359 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1363 if(stat_secrets_total)
1365 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1366 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1369 panel_size.x += panel_bg_padding * 2; // restore initial width
1374 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1377 RANKINGS_RECEIVED_CNT = 0;
1378 for (i=RANKINGS_CNT-1; i>=0; --i)
1380 ++RANKINGS_RECEIVED_CNT;
1382 if (RANKINGS_RECEIVED_CNT == 0)
1385 vector hl_rgb = rgb + '0.5 0.5 0.5';
1387 pos.y += hud_fontsize.y;
1388 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1389 pos.y += 1.25 * hud_fontsize.y;
1390 if(panel.current_panel_bg != "0")
1391 pos.y += panel_bg_border;
1396 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1398 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1403 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1405 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1409 float ranksize = 3 * hud_fontsize.x;
1410 float timesize = 5 * hud_fontsize.x;
1411 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1412 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1413 columns = min(columns, RANKINGS_RECEIVED_CNT);
1415 // expand name column to fill the entire row
1416 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1417 namesize += available_space;
1418 columnsize.x += available_space;
1420 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1421 panel_size.y += panel_bg_padding * 2;
1425 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1426 if(panel.current_panel_bg != "0")
1427 end_pos.y += panel_bg_border * 2;
1429 if(panel_bg_padding)
1431 panel_pos += '1 1 0' * panel_bg_padding;
1432 panel_size -= '2 2 0' * panel_bg_padding;
1438 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1440 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1442 int column = 0, j = 0;
1443 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1450 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1451 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1452 else if(!((j + column) & 1) && sbt_highlight)
1453 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1455 str = count_ordinal(i+1);
1456 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1457 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1458 str = ColorTranslateRGB(grecordholder[i]);
1460 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1461 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1463 pos.y += 1.25 * hud_fontsize.y;
1465 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1469 pos.x += panel_size.x / columns;
1470 pos.y = panel_pos.y;
1474 panel_size.x += panel_bg_padding * 2; // restore initial width
1478 bool have_weapon_stats;
1479 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1481 if (gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || gametype == MAPINFO_TYPE_NEXBALL)
1483 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1486 if (!have_weapon_stats)
1488 FOREACH(Weapons, it != WEP_Null, {
1489 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1490 if (weapon_stats >= 0)
1492 have_weapon_stats = true;
1496 if (!have_weapon_stats)
1503 void Scoreboard_Draw()
1505 if(!autocvar__hud_configure)
1507 if(!hud_draw_maximized) return;
1509 // frametime checks allow to toggle the scoreboard even when the game is paused
1510 if(scoreboard_active) {
1511 if(hud_configure_menu_open == 1)
1512 scoreboard_fade_alpha = 1;
1513 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1514 if (scoreboard_fadeinspeed && frametime)
1515 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1517 scoreboard_fade_alpha = 1;
1518 if(hud_fontsize_str != autocvar_hud_fontsize)
1520 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1521 Scoreboard_initFieldSizes();
1522 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1526 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1527 if (scoreboard_fadeoutspeed && frametime)
1528 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1530 scoreboard_fade_alpha = 0;
1533 if (!scoreboard_fade_alpha)
1537 scoreboard_fade_alpha = 0;
1539 if (autocvar_hud_panel_scoreboard_dynamichud)
1542 HUD_Scale_Disable();
1544 if(scoreboard_fade_alpha <= 0)
1546 panel_fade_alpha *= scoreboard_fade_alpha;
1547 HUD_Panel_LoadCvars();
1549 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1550 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1551 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1552 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1553 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1554 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1556 // don't overlap with con_notify
1557 if(!autocvar__hud_configure)
1558 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1560 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1561 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1562 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1563 panel_size.x = fixed_scoreboard_width;
1565 Scoreboard_UpdatePlayerTeams();
1567 vector pos = panel_pos;
1573 vector sb_heading_fontsize;
1574 sb_heading_fontsize = hud_fontsize * 2;
1575 draw_beginBoldFont();
1576 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1579 pos.y += sb_heading_fontsize.y;
1580 if(panel.current_panel_bg != "0")
1581 pos.y += panel_bg_border;
1583 // Draw the scoreboard
1584 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1587 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1591 vector panel_bg_color_save = panel_bg_color;
1592 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1593 if(panel.current_panel_bg != "0")
1594 team_score_baseoffset.x -= panel_bg_border;
1595 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1597 if(tm.team == NUM_SPECTATOR)
1602 draw_beginBoldFont();
1603 vector rgb = Team_ColorRGB(tm.team);
1604 str = ftos(tm.(teamscores(ts_primary)));
1605 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1606 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1608 if(ts_primary != ts_secondary)
1610 str = ftos(tm.(teamscores(ts_secondary)));
1611 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1612 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1615 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1616 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1617 else if(panel_bg_color_team > 0)
1618 panel_bg_color = rgb * panel_bg_color_team;
1620 panel_bg_color = rgb;
1621 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1623 panel_bg_color = panel_bg_color_save;
1627 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1628 if(tm.team != NUM_SPECTATOR)
1630 // display it anyway
1631 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1634 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1635 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1637 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1638 if(race_speedaward) {
1639 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);
1640 pos.y += 1.25 * hud_fontsize.y;
1642 if(race_speedaward_alltimebest) {
1643 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);
1644 pos.y += 1.25 * hud_fontsize.y;
1646 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1649 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1652 for(pl = players.sort_next; pl; pl = pl.sort_next)
1654 if(pl.team == NUM_SPECTATOR)
1656 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1657 if(tm.team == NUM_SPECTATOR)
1659 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1660 draw_beginBoldFont();
1661 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1663 pos.y += 1.25 * hud_fontsize.y;
1665 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1666 pos.y += 1.25 * hud_fontsize.y;
1672 // Print info string
1674 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1675 tl = STAT(TIMELIMIT);
1676 fl = STAT(FRAGLIMIT);
1677 ll = STAT(LEADLIMIT);
1678 if(gametype == MAPINFO_TYPE_LMS)
1681 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1686 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1690 str = strcat(str, _(" or"));
1693 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1694 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1695 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1696 TranslateScoresLabel(teamscores_label(ts_primary))));
1700 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1701 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1702 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1703 TranslateScoresLabel(scores_label(ps_primary))));
1708 if(tl > 0 || fl > 0)
1709 str = strcat(str, _(" or"));
1712 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1713 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1714 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1715 TranslateScoresLabel(teamscores_label(ts_primary))));
1719 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1720 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1721 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1722 TranslateScoresLabel(scores_label(ps_primary))));
1727 pos.y += 1.2 * hud_fontsize.y;
1728 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1730 // print information about respawn status
1731 float respawn_time = STAT(RESPAWN_TIME);
1735 if(respawn_time < 0)
1737 // a negative number means we are awaiting respawn, time value is still the same
1738 respawn_time *= -1; // remove mark now that we checked it
1740 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1741 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1743 str = sprintf(_("^1Respawning in ^3%s^1..."),
1744 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1745 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1747 count_seconds(ceil(respawn_time - time))
1751 else if(time < respawn_time)
1753 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1754 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1755 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1757 count_seconds(ceil(respawn_time - time))
1761 else if(time >= respawn_time)
1762 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1764 pos.y += 1.2 * hud_fontsize.y;
1765 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1768 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;