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 bool have_weapon_stats;
1473 void Scoreboard_Draw()
1475 if(!autocvar__hud_configure)
1477 if(!hud_draw_maximized) return;
1479 // frametime checks allow to toggle the scoreboard even when the game is paused
1480 if(scoreboard_active) {
1481 if(hud_configure_menu_open == 1)
1482 scoreboard_fade_alpha = 1;
1483 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1484 if (scoreboard_fadeinspeed && frametime)
1485 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1487 scoreboard_fade_alpha = 1;
1488 if(hud_fontsize_str != autocvar_hud_fontsize)
1490 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1491 Scoreboard_initFieldSizes();
1492 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1496 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1497 if (scoreboard_fadeoutspeed && frametime)
1498 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1500 scoreboard_fade_alpha = 0;
1503 if (!scoreboard_fade_alpha)
1507 scoreboard_fade_alpha = 0;
1509 if (autocvar_hud_panel_scoreboard_dynamichud)
1512 HUD_Scale_Disable();
1514 if(scoreboard_fade_alpha <= 0)
1516 panel_fade_alpha *= scoreboard_fade_alpha;
1517 HUD_Panel_LoadCvars();
1519 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1520 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1521 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1522 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1523 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1524 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1526 // don't overlap with con_notify
1527 if(!autocvar__hud_configure)
1528 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1530 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1531 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1532 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1533 panel_size.x = fixed_scoreboard_width;
1535 Scoreboard_UpdatePlayerTeams();
1537 vector pos = panel_pos;
1543 vector sb_heading_fontsize;
1544 sb_heading_fontsize = hud_fontsize * 2;
1545 draw_beginBoldFont();
1546 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1549 pos.y += sb_heading_fontsize.y;
1550 if(panel.current_panel_bg != "0")
1551 pos.y += panel_bg_border;
1553 // Draw the scoreboard
1554 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1557 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1561 vector panel_bg_color_save = panel_bg_color;
1562 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1563 if(panel.current_panel_bg != "0")
1564 team_score_baseoffset.x -= panel_bg_border;
1565 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1567 if(tm.team == NUM_SPECTATOR)
1572 draw_beginBoldFont();
1573 vector rgb = Team_ColorRGB(tm.team);
1574 str = ftos(tm.(teamscores(ts_primary)));
1575 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1576 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1578 if(ts_primary != ts_secondary)
1580 str = ftos(tm.(teamscores(ts_secondary)));
1581 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1582 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1585 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1586 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1587 else if(panel_bg_color_team > 0)
1588 panel_bg_color = rgb * panel_bg_color_team;
1590 panel_bg_color = rgb;
1591 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1593 panel_bg_color = panel_bg_color_save;
1597 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1598 if(tm.team != NUM_SPECTATOR)
1600 // display it anyway
1601 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1604 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1606 if (!have_weapon_stats)
1608 FOREACH(Weapons, it != WEP_Null, {
1609 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1610 if (weapon_stats >= 0)
1612 have_weapon_stats = true;
1618 if (have_weapon_stats)
1619 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && pos.y > 0.91 * vid_conheight)
1620 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1622 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1623 if(race_speedaward) {
1624 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);
1625 pos.y += 1.25 * hud_fontsize.y;
1627 if(race_speedaward_alltimebest) {
1628 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);
1629 pos.y += 1.25 * hud_fontsize.y;
1631 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1634 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1637 for(pl = players.sort_next; pl; pl = pl.sort_next)
1639 if(pl.team == NUM_SPECTATOR)
1641 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1642 if(tm.team == NUM_SPECTATOR)
1644 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1645 draw_beginBoldFont();
1646 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1648 pos.y += 1.25 * hud_fontsize.y;
1650 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1651 pos.y += 1.25 * hud_fontsize.y;
1657 // Print info string
1659 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1660 tl = STAT(TIMELIMIT);
1661 fl = STAT(FRAGLIMIT);
1662 ll = STAT(LEADLIMIT);
1663 if(gametype == MAPINFO_TYPE_LMS)
1666 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1671 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1675 str = strcat(str, _(" or"));
1678 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1679 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1680 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1681 TranslateScoresLabel(teamscores_label(ts_primary))));
1685 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1686 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1687 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1688 TranslateScoresLabel(scores_label(ps_primary))));
1693 if(tl > 0 || fl > 0)
1694 str = strcat(str, _(" or"));
1697 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1698 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1699 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1700 TranslateScoresLabel(teamscores_label(ts_primary))));
1704 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1705 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1706 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1707 TranslateScoresLabel(scores_label(ps_primary))));
1712 pos.y += 1.2 * hud_fontsize.y;
1713 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1715 // print information about respawn status
1716 float respawn_time = STAT(RESPAWN_TIME);
1720 if(respawn_time < 0)
1722 // a negative number means we are awaiting respawn, time value is still the same
1723 respawn_time *= -1; // remove mark now that we checked it
1725 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1726 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1728 str = sprintf(_("^1Respawning in ^3%s^1..."),
1729 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1730 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1732 count_seconds(ceil(respawn_time - time))
1736 else if(time < respawn_time)
1738 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1739 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1740 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1742 count_seconds(ceil(respawn_time - time))
1746 else if(time >= respawn_time)
1747 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1749 pos.y += 1.2 * hud_fontsize.y;
1750 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1753 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;