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);
1028 pos.x += column_width;
1029 pos.x += hud_fontsize.x;
1031 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1034 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1036 int max_players = 999;
1037 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1039 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1042 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1043 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1044 height /= team_count;
1047 height -= panel_bg_padding * 2; // - padding
1048 max_players = floor(height / (hud_fontsize.y * 1.25));
1049 if(max_players <= 1)
1051 if(max_players == tm.team_size)
1056 entity me = playerslots[current_player];
1058 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1059 panel_size.y += panel_bg_padding * 2;
1062 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1063 if(panel.current_panel_bg != "0")
1064 end_pos.y += panel_bg_border * 2;
1066 if(panel_bg_padding)
1068 panel_pos += '1 1 0' * panel_bg_padding;
1069 panel_size -= '2 2 0' * panel_bg_padding;
1073 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1077 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1079 pos.y += 1.25 * hud_fontsize.y;
1082 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1084 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1087 // print header row and highlight columns
1088 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1090 // fill the table and draw the rows
1091 bool is_self = false;
1092 bool self_shown = false;
1094 for(pl = players.sort_next; pl; pl = pl.sort_next)
1096 if(pl.team != tm.team)
1098 if(i == max_players - 2 && pl != me)
1100 if(!self_shown && me.team == tm.team)
1102 Scoreboard_DrawItem(pos, rgb, me, true, i);
1104 pos.y += 1.25 * hud_fontsize.y;
1108 if(i >= max_players - 1)
1110 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1113 is_self = (pl.sv_entnum == current_player);
1114 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1117 pos.y += 1.25 * hud_fontsize.y;
1121 panel_size.x += panel_bg_padding * 2; // restore initial width
1125 bool Scoreboard_WouldDraw()
1127 if (MUTATOR_CALLHOOK(DrawScoreboard))
1129 else if (QuickMenu_IsOpened())
1131 else if (HUD_Radar_Clickable())
1133 else if (scoreboard_showscores)
1135 else if (intermission == 1)
1137 else if (intermission == 2)
1139 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1141 else if (scoreboard_showscores_force)
1146 float average_accuracy;
1147 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1149 WepSet weapons_stat = WepSet_GetFromStat();
1150 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1151 int disownedcnt = 0;
1153 FOREACH(Weapons, it != WEP_Null, {
1154 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1156 WepSet set = it.m_wepset;
1157 if(it.spawnflags & WEP_TYPE_OTHER)
1162 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1164 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1171 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1172 if (weapon_cnt <= 0) return pos;
1175 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1177 int columnns = ceil(weapon_cnt / rows);
1179 float weapon_height = 29;
1180 float height = hud_fontsize.y + weapon_height;
1182 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1183 pos.y += 1.25 * hud_fontsize.y;
1184 if(panel.current_panel_bg != "0")
1185 pos.y += panel_bg_border;
1188 panel_size.y = height * rows;
1189 panel_size.y += panel_bg_padding * 2;
1192 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1193 if(panel.current_panel_bg != "0")
1194 end_pos.y += panel_bg_border * 2;
1196 if(panel_bg_padding)
1198 panel_pos += '1 1 0' * panel_bg_padding;
1199 panel_size -= '2 2 0' * panel_bg_padding;
1203 vector tmp = panel_size;
1205 float weapon_width = tmp.x / columnns / rows;
1208 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1212 // column highlighting
1213 for (int i = 0; i < columnns; ++i)
1215 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1218 for (int i = 0; i < rows; ++i)
1219 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1222 average_accuracy = 0;
1223 int weapons_with_stats = 0;
1225 pos.x += weapon_width / 2;
1227 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1230 Accuracy_LoadColors();
1232 float oldposx = pos.x;
1236 FOREACH(Weapons, it != WEP_Null, {
1237 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1239 WepSet set = it.m_wepset;
1240 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1242 if (it.spawnflags & WEP_TYPE_OTHER)
1246 if (weapon_stats >= 0)
1247 weapon_alpha = sbt_fg_alpha;
1249 weapon_alpha = 0.2 * sbt_fg_alpha;
1252 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1254 if (weapon_stats >= 0) {
1255 weapons_with_stats += 1;
1256 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1259 s = sprintf("%d%%", weapon_stats * 100);
1262 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1264 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1265 rgb = Accuracy_GetColor(weapon_stats);
1267 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1269 tmpos.x += weapon_width * rows;
1270 pos.x += weapon_width * rows;
1271 if (rows == 2 && column == columnns - 1) {
1279 if (weapons_with_stats)
1280 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1282 panel_size.x += panel_bg_padding * 2; // restore initial width
1286 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1288 pos.x += hud_fontsize.x * 0.25;
1289 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1290 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1291 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1293 pos.y += hud_fontsize.y;
1298 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1299 float stat_secrets_found, stat_secrets_total;
1300 float stat_monsters_killed, stat_monsters_total;
1304 // get monster stats
1305 stat_monsters_killed = STAT(MONSTERS_KILLED);
1306 stat_monsters_total = STAT(MONSTERS_TOTAL);
1308 // get secrets stats
1309 stat_secrets_found = STAT(SECRETS_FOUND);
1310 stat_secrets_total = STAT(SECRETS_TOTAL);
1312 // get number of rows
1313 if(stat_secrets_total)
1315 if(stat_monsters_total)
1318 // if no rows, return
1322 // draw table header
1323 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1324 pos.y += 1.25 * hud_fontsize.y;
1325 if(panel.current_panel_bg != "0")
1326 pos.y += panel_bg_border;
1329 panel_size.y = hud_fontsize.y * rows;
1330 panel_size.y += panel_bg_padding * 2;
1333 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1334 if(panel.current_panel_bg != "0")
1335 end_pos.y += panel_bg_border * 2;
1337 if(panel_bg_padding)
1339 panel_pos += '1 1 0' * panel_bg_padding;
1340 panel_size -= '2 2 0' * panel_bg_padding;
1344 vector tmp = panel_size;
1347 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1350 if(stat_monsters_total)
1352 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1353 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1357 if(stat_secrets_total)
1359 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1360 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1363 panel_size.x += panel_bg_padding * 2; // restore initial width
1368 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1371 RANKINGS_RECEIVED_CNT = 0;
1372 for (i=RANKINGS_CNT-1; i>=0; --i)
1374 ++RANKINGS_RECEIVED_CNT;
1376 if (RANKINGS_RECEIVED_CNT == 0)
1379 vector hl_rgb = rgb + '0.5 0.5 0.5';
1381 pos.y += hud_fontsize.y;
1382 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1383 pos.y += 1.25 * hud_fontsize.y;
1384 if(panel.current_panel_bg != "0")
1385 pos.y += panel_bg_border;
1390 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1392 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1397 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1399 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1403 float ranksize = 3 * hud_fontsize.x;
1404 float timesize = 5 * hud_fontsize.x;
1405 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1406 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1407 columns = min(columns, RANKINGS_RECEIVED_CNT);
1409 // expand name column to fill the entire row
1410 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1411 namesize += available_space;
1412 columnsize.x += available_space;
1414 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1415 panel_size.y += panel_bg_padding * 2;
1419 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1420 if(panel.current_panel_bg != "0")
1421 end_pos.y += panel_bg_border * 2;
1423 if(panel_bg_padding)
1425 panel_pos += '1 1 0' * panel_bg_padding;
1426 panel_size -= '2 2 0' * panel_bg_padding;
1432 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1434 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1436 int column = 0, j = 0;
1437 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1444 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1445 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1446 else if(!((j + column) & 1) && sbt_highlight)
1447 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1449 str = count_ordinal(i+1);
1450 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1451 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 str = ColorTranslateRGB(grecordholder[i]);
1454 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1455 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1457 pos.y += 1.25 * hud_fontsize.y;
1459 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1463 pos.x += panel_size.x / columns;
1464 pos.y = panel_pos.y;
1468 panel_size.x += panel_bg_padding * 2; // restore initial width
1472 void Scoreboard_Draw()
1474 if(!autocvar__hud_configure)
1476 if(!hud_draw_maximized) return;
1478 // frametime checks allow to toggle the scoreboard even when the game is paused
1479 if(scoreboard_active) {
1480 if(hud_configure_menu_open == 1)
1481 scoreboard_fade_alpha = 1;
1482 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1483 if (scoreboard_fadeinspeed && frametime)
1484 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1486 scoreboard_fade_alpha = 1;
1487 if(hud_fontsize_str != autocvar_hud_fontsize)
1489 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1490 Scoreboard_initFieldSizes();
1491 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1495 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1496 if (scoreboard_fadeoutspeed && frametime)
1497 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1499 scoreboard_fade_alpha = 0;
1502 if (!scoreboard_fade_alpha)
1506 scoreboard_fade_alpha = 0;
1508 if (autocvar_hud_panel_scoreboard_dynamichud)
1511 HUD_Scale_Disable();
1513 if(scoreboard_fade_alpha <= 0)
1515 panel_fade_alpha *= scoreboard_fade_alpha;
1516 HUD_Panel_LoadCvars();
1518 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1519 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1520 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1521 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1522 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1523 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1525 // don't overlap with con_notify
1526 if(!autocvar__hud_configure)
1527 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1529 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1530 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1531 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1532 panel_size.x = fixed_scoreboard_width;
1534 Scoreboard_UpdatePlayerTeams();
1536 vector pos = panel_pos;
1542 vector sb_heading_fontsize;
1543 sb_heading_fontsize = hud_fontsize * 2;
1544 draw_beginBoldFont();
1545 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1548 pos.y += sb_heading_fontsize.y;
1549 if(panel.current_panel_bg != "0")
1550 pos.y += panel_bg_border;
1552 // Draw the scoreboard
1553 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1556 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1560 vector panel_bg_color_save = panel_bg_color;
1561 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1562 if(panel.current_panel_bg != "0")
1563 team_score_baseoffset.x -= panel_bg_border;
1564 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1566 if(tm.team == NUM_SPECTATOR)
1571 draw_beginBoldFont();
1572 vector rgb = Team_ColorRGB(tm.team);
1573 str = ftos(tm.(teamscores(ts_primary)));
1574 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1575 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1577 if(ts_primary != ts_secondary)
1579 str = ftos(tm.(teamscores(ts_secondary)));
1580 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1581 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1584 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1585 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1586 else if(panel_bg_color_team > 0)
1587 panel_bg_color = rgb * panel_bg_color_team;
1589 panel_bg_color = rgb;
1590 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1592 panel_bg_color = panel_bg_color_save;
1596 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1597 if(tm.team != NUM_SPECTATOR)
1599 // display it anyway
1600 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1603 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1605 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1606 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1608 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1609 if(race_speedaward) {
1610 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);
1611 pos.y += 1.25 * hud_fontsize.y;
1613 if(race_speedaward_alltimebest) {
1614 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);
1615 pos.y += 1.25 * hud_fontsize.y;
1617 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1620 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1623 for(pl = players.sort_next; pl; pl = pl.sort_next)
1625 if(pl.team == NUM_SPECTATOR)
1627 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1628 if(tm.team == NUM_SPECTATOR)
1630 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1631 draw_beginBoldFont();
1632 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1634 pos.y += 1.25 * hud_fontsize.y;
1636 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1637 pos.y += 1.25 * hud_fontsize.y;
1643 // Print info string
1645 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1646 tl = STAT(TIMELIMIT);
1647 fl = STAT(FRAGLIMIT);
1648 ll = STAT(LEADLIMIT);
1649 if(gametype == MAPINFO_TYPE_LMS)
1652 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1657 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1661 str = strcat(str, _(" or"));
1664 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1665 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1666 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1667 TranslateScoresLabel(teamscores_label(ts_primary))));
1671 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1672 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1673 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1674 TranslateScoresLabel(scores_label(ps_primary))));
1679 if(tl > 0 || fl > 0)
1680 str = strcat(str, _(" or"));
1683 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1684 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1685 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1686 TranslateScoresLabel(teamscores_label(ts_primary))));
1690 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1691 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1692 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1693 TranslateScoresLabel(scores_label(ps_primary))));
1698 pos.y += 1.2 * hud_fontsize.y;
1699 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1701 // print information about respawn status
1702 float respawn_time = STAT(RESPAWN_TIME);
1706 if(respawn_time < 0)
1708 // a negative number means we are awaiting respawn, time value is still the same
1709 respawn_time *= -1; // remove mark now that we checked it
1711 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1712 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1714 str = sprintf(_("^1Respawning in ^3%s^1..."),
1715 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1716 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1718 count_seconds(ceil(respawn_time - time))
1722 else if(time < respawn_time)
1724 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1725 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1726 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1728 count_seconds(ceil(respawn_time - time))
1732 else if(time >= respawn_time)
1733 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1735 pos.y += 1.2 * hud_fontsize.y;
1736 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1739 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;