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 float autocvar_hud_panel_scoreboard_accuracy_showdelay = 2;
65 float autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos = 0.75;
67 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
69 bool autocvar_hud_panel_scoreboard_dynamichud = false;
71 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
72 bool autocvar_hud_panel_scoreboard_others_showscore = true;
73 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
74 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
75 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
77 // wrapper to put all possible scores titles through gettext
78 string TranslateScoresLabel(string l)
82 case "bckills": return CTX(_("SCO^bckills"));
83 case "bctime": return CTX(_("SCO^bctime"));
84 case "caps": return CTX(_("SCO^caps"));
85 case "captime": return CTX(_("SCO^captime"));
86 case "deaths": return CTX(_("SCO^deaths"));
87 case "destroyed": return CTX(_("SCO^destroyed"));
88 case "dmg": return CTX(_("SCO^damage"));
89 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
90 case "drops": return CTX(_("SCO^drops"));
91 case "faults": return CTX(_("SCO^faults"));
92 case "fckills": return CTX(_("SCO^fckills"));
93 case "goals": return CTX(_("SCO^goals"));
94 case "kckills": return CTX(_("SCO^kckills"));
95 case "kdratio": return CTX(_("SCO^kdratio"));
96 case "kd": return CTX(_("SCO^k/d"));
97 case "kdr": return CTX(_("SCO^kdr"));
98 case "kills": return CTX(_("SCO^kills"));
99 case "teamkills": return CTX(_("SCO^teamkills"));
100 case "laps": return CTX(_("SCO^laps"));
101 case "lives": return CTX(_("SCO^lives"));
102 case "losses": return CTX(_("SCO^losses"));
103 case "name": return CTX(_("SCO^name"));
104 case "sum": return CTX(_("SCO^sum"));
105 case "nick": return CTX(_("SCO^nick"));
106 case "objectives": return CTX(_("SCO^objectives"));
107 case "pickups": return CTX(_("SCO^pickups"));
108 case "ping": return CTX(_("SCO^ping"));
109 case "pl": return CTX(_("SCO^pl"));
110 case "pushes": return CTX(_("SCO^pushes"));
111 case "rank": return CTX(_("SCO^rank"));
112 case "returns": return CTX(_("SCO^returns"));
113 case "revivals": return CTX(_("SCO^revivals"));
114 case "rounds": return CTX(_("SCO^rounds won"));
115 case "score": return CTX(_("SCO^score"));
116 case "suicides": return CTX(_("SCO^suicides"));
117 case "takes": return CTX(_("SCO^takes"));
118 case "ticks": return CTX(_("SCO^ticks"));
123 void Scoreboard_InitScores()
127 ps_primary = ps_secondary = NULL;
128 ts_primary = ts_secondary = -1;
129 FOREACH(Scores, true, {
130 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
131 if(f == SFL_SORT_PRIO_PRIMARY)
133 if(f == SFL_SORT_PRIO_SECONDARY)
136 if(ps_secondary == NULL)
137 ps_secondary = ps_primary;
139 for(i = 0; i < MAX_TEAMSCORE; ++i)
141 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
142 if(f == SFL_SORT_PRIO_PRIMARY)
144 if(f == SFL_SORT_PRIO_SECONDARY)
147 if(ts_secondary == -1)
148 ts_secondary = ts_primary;
150 Cmd_Scoreboard_SetFields(0);
154 void Scoreboard_UpdatePlayerTeams()
158 for(pl = players.sort_next; pl; pl = pl.sort_next)
161 int Team = entcs_GetScoreTeam(pl.sv_entnum);
162 if(SetTeam(pl, Team))
165 Scoreboard_UpdatePlayerPos(pl);
169 pl = players.sort_next;
174 print(strcat("PNUM: ", ftos(num), "\n"));
179 int Scoreboard_CompareScore(int vl, int vr, int f)
181 TC(int, vl); TC(int, vr); TC(int, f);
182 if(f & SFL_ZERO_IS_WORST)
184 if(vl == 0 && vr != 0)
186 if(vl != 0 && vr == 0)
190 return IS_INCREASING(f);
192 return IS_DECREASING(f);
196 float Scoreboard_ComparePlayerScores(entity left, entity right)
199 vl = entcs_GetTeam(left.sv_entnum);
200 vr = entcs_GetTeam(right.sv_entnum);
212 if(vl == NUM_SPECTATOR)
214 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
216 if(!left.gotscores && right.gotscores)
221 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
225 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
229 FOREACH(Scores, true, {
230 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
231 if (r >= 0) return r;
234 if (left.sv_entnum < right.sv_entnum)
240 void Scoreboard_UpdatePlayerPos(entity player)
243 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
245 SORT_SWAP(player, ent);
247 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
249 SORT_SWAP(ent, player);
253 float Scoreboard_CompareTeamScores(entity left, entity right)
257 if(left.team == NUM_SPECTATOR)
259 if(right.team == NUM_SPECTATOR)
262 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
266 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
270 for(i = 0; i < MAX_TEAMSCORE; ++i)
272 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
277 if (left.team < right.team)
283 void Scoreboard_UpdateTeamPos(entity Team)
286 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
288 SORT_SWAP(Team, ent);
290 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
292 SORT_SWAP(ent, Team);
296 void Cmd_Scoreboard_Help()
298 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
299 LOG_INFO(_("Usage:"));
300 LOG_INFO("^2scoreboard_columns_set ^3default");
301 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
302 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
303 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
304 LOG_INFO(_("^2scoreboard_columns_set ^3expand_default ^7loads default layout and expands it into the cvar scoreboard_columns so you can edit it"));
305 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
306 LOG_INFO(_("The following field names are recognized (case insensitive):"));
309 LOG_INFO(strcat("^3name^7 ", _("Name of a player")));
310 LOG_INFO(strcat("^3nick^7 ", _("Name of a player")));
311 LOG_INFO(strcat("^3ping^7 ", _("Ping time")));
312 LOG_INFO(strcat("^3pl^7 ", _("Packet loss")));
313 LOG_INFO(strcat("^3elo^7 ", _("Player ELO")));
314 LOG_INFO(strcat("^3fps^7 ", _("Player FPS")));
315 LOG_INFO(strcat("^3kills^7 ", _("Number of kills")));
316 LOG_INFO(strcat("^3deaths^7 ", _("Number of deaths")));
317 LOG_INFO(strcat("^3suicides^7 ", _("Number of suicides")));
318 LOG_INFO(strcat("^3frags^7 ", _("kills - suicides")));
319 LOG_INFO(strcat("^3teamkills^7 ", _("Number of teamkills")));
320 LOG_INFO(strcat("^3kd^7 ", _("The kill-death ratio")));
321 LOG_INFO(strcat("^3dmg^7 ", _("The total damage done")));
322 LOG_INFO(strcat("^3dmgtaken^7 ", _("The total damage taken")));
323 LOG_INFO(strcat("^3sum^7 ", _("kills - deaths")));
324 LOG_INFO(strcat("^3caps^7 ", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
325 LOG_INFO(strcat("^3pickups^7 ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
326 LOG_INFO(strcat("^3captime^7 ", _("Time of fastest cap (CTF)")));
327 LOG_INFO(strcat("^3fckills^7 ", _("Number of flag carrier kills")));
328 LOG_INFO(strcat("^3returns^7 ", _("Number of flag returns")));
329 LOG_INFO(strcat("^3drops^7 ", _("Number of flag drops")));
330 LOG_INFO(strcat("^3lives^7 ", _("Number of lives (LMS)")));
331 LOG_INFO(strcat("^3rank^7 ", _("Player rank")));
332 LOG_INFO(strcat("^3pushes^7 ", _("Number of players pushed into void")));
333 LOG_INFO(strcat("^3destroyed^7 ", _("Number of keys destroyed by pushing them into void")));
334 LOG_INFO(strcat("^3kckills^7 ", _("Number of keys carrier kills")));
335 LOG_INFO(strcat("^3losses^7 ", _("Number of times a key was lost")));
336 LOG_INFO(strcat("^3laps^7 ", _("Number of laps finished (race/cts)")));
337 LOG_INFO(strcat("^3time^7 ", _("Total time raced (race/cts)")));
338 LOG_INFO(strcat("^3fastest^7 ", _("Time of fastest lap (race/cts)")));
339 LOG_INFO(strcat("^3ticks^7 ", _("Number of ticks (DOM)")));
340 LOG_INFO(strcat("^3takes^7 ", _("Number of domination points taken (DOM)")));
341 LOG_INFO(strcat("^3bckills^7 ", _("Number of ball carrier kills")));
342 LOG_INFO(strcat("^3bctime^7 ", _("Total amount of time holding the ball in Keepaway")));
343 LOG_INFO(strcat("^3score^7 ", _("Total score")));
346 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
347 "of game types, then a slash, to make the field show up only in these\n"
348 "or in all but these game types. You can also specify 'all' as a\n"
349 "field to show all fields available for the current game mode."));
352 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
353 "include/exclude ALL teams/noteams game modes."));
356 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
357 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
358 "right of the vertical bar aligned to the right."));
359 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
360 "other gamemodes except DM."));
363 // NOTE: adding a gametype with ? to not warn for an optional field
364 // make sure it's excluded in a previous exclusive rule, if any
365 // otherwise the previous exclusive rule warns anyway
366 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
367 #define SCOREBOARD_DEFAULT_COLUMNS \
368 "ping pl fps name |" \
369 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
370 " -teams,lms/deaths +ft,tdm/deaths" \
372 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
373 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
374 " +tdm,ft,dom,ons,as/teamkills"\
375 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
376 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
377 " +lms/lives +lms/rank" \
378 " +kh/kckills +kh/losses +kh/caps" \
379 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
380 " +as/objectives +nb/faults +nb/goals" \
381 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
382 " +dom/ticks +dom/takes" \
383 " -lms,rc,cts,inv,nb/score"
385 void Cmd_Scoreboard_SetFields(int argc)
390 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
394 return; // do nothing, we don't know gametype and scores yet
396 // sbt_fields uses strunzone on the titles!
397 if(!sbt_field_title[0])
398 for(i = 0; i < MAX_SBT_FIELDS; ++i)
399 sbt_field_title[i] = strzone("(null)");
401 // TODO: re enable with gametype dependant cvars?
402 if(argc < 3) // no arguments provided
403 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
406 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
410 if(argv(2) == "default" || argv(2) == "expand_default")
412 if(argv(2) == "expand_default")
413 cvar_set("scoreboard_columns", SCOREBOARD_DEFAULT_COLUMNS);
414 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
416 else if(argv(2) == "all")
418 string s = "ping pl name |"; // scores without a label
419 FOREACH(Scores, true, {
421 if(it != ps_secondary)
422 if(scores_label(it) != "")
423 s = strcat(s, " ", scores_label(it));
425 if(ps_secondary != ps_primary)
426 s = strcat(s, " ", scores_label(ps_secondary));
427 s = strcat(s, " ", scores_label(ps_primary));
428 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
435 hud_fontsize = HUD_GetFontsize("hud_fontsize");
437 for(i = 1; i < argc - 1; ++i)
440 bool nocomplain = false;
441 if(substring(str, 0, 1) == "?")
444 str = substring(str, 1, strlen(str) - 1);
447 slash = strstrofs(str, "/", 0);
450 pattern = substring(str, 0, slash);
451 str = substring(str, slash + 1, strlen(str) - (slash + 1));
453 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
457 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
458 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
459 str = strtolower(str);
464 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
465 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
466 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
467 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
468 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
469 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
470 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
471 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
472 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
473 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
476 FOREACH(Scores, true, {
477 if (str == strtolower(scores_label(it))) {
479 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
489 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
493 sbt_field[sbt_num_fields] = j;
496 if(j == ps_secondary)
497 have_secondary = true;
502 if(sbt_num_fields >= MAX_SBT_FIELDS)
506 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
508 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
509 have_secondary = true;
510 if(ps_primary == ps_secondary)
511 have_secondary = true;
512 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
514 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
518 strunzone(sbt_field_title[sbt_num_fields]);
519 for(i = sbt_num_fields; i > 0; --i)
521 sbt_field_title[i] = sbt_field_title[i-1];
522 sbt_field_size[i] = sbt_field_size[i-1];
523 sbt_field[i] = sbt_field[i-1];
525 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
526 sbt_field[0] = SP_NAME;
528 LOG_INFO("fixed missing field 'name'");
532 strunzone(sbt_field_title[sbt_num_fields]);
533 for(i = sbt_num_fields; i > 1; --i)
535 sbt_field_title[i] = sbt_field_title[i-1];
536 sbt_field_size[i] = sbt_field_size[i-1];
537 sbt_field[i] = sbt_field[i-1];
539 sbt_field_title[1] = strzone("|");
540 sbt_field[1] = SP_SEPARATOR;
541 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
543 LOG_INFO("fixed missing field '|'");
546 else if(!have_separator)
548 strcpy(sbt_field_title[sbt_num_fields], "|");
549 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
550 sbt_field[sbt_num_fields] = SP_SEPARATOR;
552 LOG_INFO("fixed missing field '|'");
556 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
557 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
558 sbt_field[sbt_num_fields] = ps_secondary;
560 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
564 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
565 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
566 sbt_field[sbt_num_fields] = ps_primary;
568 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
572 sbt_field[sbt_num_fields] = SP_END;
576 vector sbt_field_rgb;
577 string sbt_field_icon0;
578 string sbt_field_icon1;
579 string sbt_field_icon2;
580 vector sbt_field_icon0_rgb;
581 vector sbt_field_icon1_rgb;
582 vector sbt_field_icon2_rgb;
583 string Scoreboard_GetName(entity pl)
585 if(ready_waiting && pl.ready)
587 sbt_field_icon0 = "gfx/scoreboard/player_ready";
591 int f = entcs_GetClientColors(pl.sv_entnum);
593 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
594 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
595 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
596 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
597 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
600 return entcs_GetName(pl.sv_entnum);
602 string Scoreboard_GetField(entity pl, PlayerScoreField field)
604 float tmp, num, denom;
607 sbt_field_rgb = '1 1 1';
608 sbt_field_icon0 = "";
609 sbt_field_icon1 = "";
610 sbt_field_icon2 = "";
611 sbt_field_icon0_rgb = '1 1 1';
612 sbt_field_icon1_rgb = '1 1 1';
613 sbt_field_icon2_rgb = '1 1 1';
618 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
619 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
623 tmp = max(0, min(220, f-80)) / 220;
624 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
630 f = pl.ping_packetloss;
631 tmp = pl.ping_movementloss;
632 if(f == 0 && tmp == 0)
634 str = ftos(ceil(f * 100));
636 str = strcat(str, "~", ftos(ceil(tmp * 100)));
637 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
638 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
642 return Scoreboard_GetName(pl);
645 f = pl.(scores(SP_KILLS));
646 f -= pl.(scores(SP_SUICIDES));
650 num = pl.(scores(SP_KILLS));
651 denom = pl.(scores(SP_DEATHS));
654 sbt_field_rgb = '0 1 0';
655 str = sprintf("%d", num);
656 } else if(num <= 0) {
657 sbt_field_rgb = '1 0 0';
658 str = sprintf("%.1f", num/denom);
660 str = sprintf("%.1f", num/denom);
664 f = pl.(scores(SP_KILLS));
665 f -= pl.(scores(SP_DEATHS));
668 sbt_field_rgb = '0 1 0';
670 sbt_field_rgb = '1 1 1';
672 sbt_field_rgb = '1 0 0';
678 float elo = pl.(scores(SP_ELO));
680 case -1: return "...";
681 case -2: return _("N/A");
682 default: return ftos(elo);
688 float fps = pl.(scores(SP_FPS));
691 sbt_field_rgb = '1 1 1';
692 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
694 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
695 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
699 case SP_DMG: case SP_DMGTAKEN:
700 return sprintf("%.1f k", pl.(scores(field)) / 1000);
702 default: case SP_SCORE:
703 tmp = pl.(scores(field));
704 f = scores_flags(field);
705 if(field == ps_primary)
706 sbt_field_rgb = '1 1 0';
707 else if(field == ps_secondary)
708 sbt_field_rgb = '0 1 1';
710 sbt_field_rgb = '1 1 1';
711 return ScoreString(f, tmp);
716 float sbt_fixcolumnwidth_len;
717 float sbt_fixcolumnwidth_iconlen;
718 float sbt_fixcolumnwidth_marginlen;
720 string Scoreboard_FixColumnWidth(int i, string str)
726 sbt_fixcolumnwidth_iconlen = 0;
728 if(sbt_field_icon0 != "")
730 sz = draw_getimagesize(sbt_field_icon0);
732 if(sbt_fixcolumnwidth_iconlen < f)
733 sbt_fixcolumnwidth_iconlen = f;
736 if(sbt_field_icon1 != "")
738 sz = draw_getimagesize(sbt_field_icon1);
740 if(sbt_fixcolumnwidth_iconlen < f)
741 sbt_fixcolumnwidth_iconlen = f;
744 if(sbt_field_icon2 != "")
746 sz = draw_getimagesize(sbt_field_icon2);
748 if(sbt_fixcolumnwidth_iconlen < f)
749 sbt_fixcolumnwidth_iconlen = f;
752 if(sbt_fixcolumnwidth_iconlen != 0)
754 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
755 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
758 sbt_fixcolumnwidth_marginlen = 0;
760 if(sbt_field[i] == SP_NAME) // name gets all remaining space
763 float remaining_space = 0;
764 for(j = 0; j < sbt_num_fields; ++j)
766 if (sbt_field[i] != SP_SEPARATOR)
767 remaining_space += sbt_field_size[j] + hud_fontsize.x;
768 sbt_field_size[i] = panel_size.x - remaining_space;
770 if (sbt_fixcolumnwidth_iconlen != 0)
771 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
772 float namesize = panel_size.x - remaining_space;
773 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
774 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
776 max_namesize = vid_conwidth - remaining_space;
779 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
781 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
782 if(sbt_field_size[i] < f)
783 sbt_field_size[i] = f;
788 void Scoreboard_initFieldSizes()
790 for(int i = 0; i < sbt_num_fields; ++i)
792 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
793 Scoreboard_FixColumnWidth(i, "");
797 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
800 vector column_dim = eY * panel_size.y;
802 column_dim.y -= 1.25 * hud_fontsize.y;
803 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
804 pos.x += hud_fontsize.x * 0.5;
805 for(i = 0; i < sbt_num_fields; ++i)
807 if(sbt_field[i] == SP_SEPARATOR)
809 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
812 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
813 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
814 pos.x += column_dim.x;
816 if(sbt_field[i] == SP_SEPARATOR)
818 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
819 for(i = sbt_num_fields - 1; i > 0; --i)
821 if(sbt_field[i] == SP_SEPARATOR)
824 pos.x -= sbt_field_size[i];
829 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
830 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
833 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
834 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
835 pos.x -= hud_fontsize.x;
840 pos.y += 1.25 * hud_fontsize.y;
844 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
846 TC(bool, is_self); TC(int, pl_number);
848 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
850 vector h_pos = item_pos;
851 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
852 // alternated rows highlighting
854 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
855 else if((sbt_highlight) && (!(pl_number % 2)))
856 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
858 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
860 vector pos = item_pos;
861 pos.x += hud_fontsize.x * 0.5;
862 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
863 vector tmp = '0 0 0';
865 PlayerScoreField field;
866 for(i = 0; i < sbt_num_fields; ++i)
868 field = sbt_field[i];
869 if(field == SP_SEPARATOR)
872 if(is_spec && field != SP_NAME && field != SP_PING) {
873 pos.x += sbt_field_size[i] + hud_fontsize.x;
876 str = Scoreboard_GetField(pl, field);
877 str = Scoreboard_FixColumnWidth(i, str);
879 pos.x += sbt_field_size[i] + hud_fontsize.x;
881 if(field == SP_NAME) {
882 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
883 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
885 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
886 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
889 tmp.x = sbt_field_size[i] + hud_fontsize.x;
890 if(sbt_field_icon0 != "")
891 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
892 if(sbt_field_icon1 != "")
893 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
894 if(sbt_field_icon2 != "")
895 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
898 if(sbt_field[i] == SP_SEPARATOR)
900 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
901 for(i = sbt_num_fields-1; i > 0; --i)
903 field = sbt_field[i];
904 if(field == SP_SEPARATOR)
907 if(is_spec && field != SP_NAME && field != SP_PING) {
908 pos.x -= sbt_field_size[i] + hud_fontsize.x;
912 str = Scoreboard_GetField(pl, field);
913 str = Scoreboard_FixColumnWidth(i, str);
915 if(field == SP_NAME) {
916 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
917 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
919 tmp.x = sbt_fixcolumnwidth_len;
920 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
923 tmp.x = sbt_field_size[i];
924 if(sbt_field_icon0 != "")
925 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
926 if(sbt_field_icon1 != "")
927 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
928 if(sbt_field_icon2 != "")
929 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
930 pos.x -= sbt_field_size[i] + hud_fontsize.x;
935 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
938 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
941 vector h_pos = item_pos;
942 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
944 bool complete = (this_team == NUM_SPECTATOR);
947 if((sbt_highlight) && (!(pl_number % 2)))
948 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
950 vector pos = item_pos;
951 pos.x += hud_fontsize.x * 0.5;
952 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
954 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
956 width_limit -= stringwidth("...", false, hud_fontsize);
957 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
958 static float max_name_width = 0;
961 float min_fieldsize = 0;
962 float fieldpadding = hud_fontsize.x * 0.25;
963 if(this_team == NUM_SPECTATOR)
965 if(autocvar_hud_panel_scoreboard_spectators_showping)
966 min_fieldsize = stringwidth("999", false, hud_fontsize);
968 else if(autocvar_hud_panel_scoreboard_others_showscore)
969 min_fieldsize = stringwidth("99", false, hud_fontsize);
970 for(i = 0; pl; pl = pl.sort_next)
972 if(pl.team != this_team)
978 if(this_team == NUM_SPECTATOR)
980 if(autocvar_hud_panel_scoreboard_spectators_showping)
981 field = Scoreboard_GetField(pl, SP_PING);
983 else if(autocvar_hud_panel_scoreboard_others_showscore)
984 field = Scoreboard_GetField(pl, SP_SCORE);
986 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
987 float column_width = stringwidth(str, true, hud_fontsize);
988 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
990 if(column_width > max_name_width)
991 max_name_width = column_width;
992 column_width = max_name_width;
996 fieldsize = stringwidth(field, false, hud_fontsize);
997 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1000 if(pos.x + column_width > width_limit)
1005 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1010 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1011 pos.y += hud_fontsize.y * 1.25;
1015 vector name_pos = pos;
1016 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1017 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1018 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1021 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1022 h_size.y = hud_fontsize.y;
1023 vector field_pos = pos;
1024 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1025 field_pos.x += column_width - h_size.x;
1027 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1028 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1029 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1033 h_size.x = column_width + hud_fontsize.x * 0.25;
1034 h_size.y = hud_fontsize.y;
1035 drawfill(pos - hud_fontsize.x * 0.25 * eX, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
1037 pos.x += column_width;
1038 pos.x += hud_fontsize.x;
1040 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1043 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1045 int max_players = 999;
1046 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1048 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1051 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1052 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1053 height /= team_count;
1056 height -= panel_bg_padding * 2; // - padding
1057 max_players = floor(height / (hud_fontsize.y * 1.25));
1058 if(max_players <= 1)
1060 if(max_players == tm.team_size)
1065 entity me = playerslots[current_player];
1067 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1068 panel_size.y += panel_bg_padding * 2;
1071 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1072 if(panel.current_panel_bg != "0")
1073 end_pos.y += panel_bg_border * 2;
1075 if(panel_bg_padding)
1077 panel_pos += '1 1 0' * panel_bg_padding;
1078 panel_size -= '2 2 0' * panel_bg_padding;
1082 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1086 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1088 pos.y += 1.25 * hud_fontsize.y;
1091 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1093 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1096 // print header row and highlight columns
1097 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1099 // fill the table and draw the rows
1100 bool is_self = false;
1101 bool self_shown = false;
1103 for(pl = players.sort_next; pl; pl = pl.sort_next)
1105 if(pl.team != tm.team)
1107 if(i == max_players - 2 && pl != me)
1109 if(!self_shown && me.team == tm.team)
1111 Scoreboard_DrawItem(pos, rgb, me, true, i);
1113 pos.y += 1.25 * hud_fontsize.y;
1117 if(i >= max_players - 1)
1119 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1122 is_self = (pl.sv_entnum == current_player);
1123 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1126 pos.y += 1.25 * hud_fontsize.y;
1130 panel_size.x += panel_bg_padding * 2; // restore initial width
1134 bool Scoreboard_WouldDraw()
1136 if (MUTATOR_CALLHOOK(DrawScoreboard))
1138 else if (QuickMenu_IsOpened())
1140 else if (HUD_Radar_Clickable())
1142 else if (scoreboard_showscores)
1144 else if (intermission == 1)
1146 else if (intermission == 2)
1148 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && !ISGAMETYPE(CTS) && !active_minigame)
1150 else if (scoreboard_showscores_force)
1155 float average_accuracy;
1156 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1160 if (scoreboard_fade_alpha == 1)
1161 scoreboard_acc_fade_alpha = min(1, scoreboard_acc_fade_alpha + frametime * 10);
1163 scoreboard_acc_fade_alpha = 1; // sync fading with the scoreboard
1165 vector initial_pos = pos;
1167 WepSet weapons_stat = WepSet_GetFromStat();
1168 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1169 int disownedcnt = 0;
1171 FOREACH(Weapons, it != WEP_Null, {
1172 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1174 WepSet set = it.m_wepset;
1175 if(it.spawnflags & WEP_TYPE_OTHER)
1180 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1182 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1189 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1190 if (weapon_cnt <= 0) return pos;
1193 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1195 int columnns = ceil(weapon_cnt / rows);
1197 float weapon_height = 29;
1198 float height = hud_fontsize.y + weapon_height;
1200 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1201 pos.y += 1.25 * hud_fontsize.y;
1202 if(panel.current_panel_bg != "0")
1203 pos.y += panel_bg_border;
1206 panel_size.y = height * rows;
1207 panel_size.y += panel_bg_padding * 2;
1209 float panel_bg_alpha_save = panel_bg_alpha;
1210 panel_bg_alpha *= scoreboard_acc_fade_alpha;
1212 panel_bg_alpha = panel_bg_alpha_save;
1214 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1215 if(panel.current_panel_bg != "0")
1216 end_pos.y += panel_bg_border * 2;
1218 if(panel_bg_padding)
1220 panel_pos += '1 1 0' * panel_bg_padding;
1221 panel_size -= '2 2 0' * panel_bg_padding;
1225 vector tmp = panel_size;
1227 float weapon_width = tmp.x / columnns / rows;
1230 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1234 // column highlighting
1235 for (int i = 0; i < columnns; ++i)
1237 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1240 for (int i = 0; i < rows; ++i)
1241 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1244 average_accuracy = 0;
1245 int weapons_with_stats = 0;
1247 pos.x += weapon_width / 2;
1249 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1252 Accuracy_LoadColors();
1254 float oldposx = pos.x;
1258 FOREACH(Weapons, it != WEP_Null, {
1259 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1261 WepSet set = it.m_wepset;
1262 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1264 if (it.spawnflags & WEP_TYPE_OTHER)
1268 if (weapon_stats >= 0)
1269 weapon_alpha = sbt_fg_alpha;
1271 weapon_alpha = 0.2 * sbt_fg_alpha;
1274 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1276 if (weapon_stats >= 0) {
1277 weapons_with_stats += 1;
1278 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1281 s = sprintf("%d%%", weapon_stats * 100);
1284 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1286 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1287 rgb = Accuracy_GetColor(weapon_stats);
1289 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha * scoreboard_acc_fade_alpha, DRAWFLAG_NORMAL);
1291 tmpos.x += weapon_width * rows;
1292 pos.x += weapon_width * rows;
1293 if (rows == 2 && column == columnns - 1) {
1301 if (weapons_with_stats)
1302 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1304 panel_size.x += panel_bg_padding * 2; // restore initial width
1306 if (scoreboard_acc_fade_alpha == 1)
1308 return initial_pos + (end_pos - initial_pos) * scoreboard_acc_fade_alpha;
1311 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1313 pos.x += hud_fontsize.x * 0.25;
1314 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1315 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1316 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1318 pos.y += hud_fontsize.y;
1323 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1324 float stat_secrets_found, stat_secrets_total;
1325 float stat_monsters_killed, stat_monsters_total;
1329 // get monster stats
1330 stat_monsters_killed = STAT(MONSTERS_KILLED);
1331 stat_monsters_total = STAT(MONSTERS_TOTAL);
1333 // get secrets stats
1334 stat_secrets_found = STAT(SECRETS_FOUND);
1335 stat_secrets_total = STAT(SECRETS_TOTAL);
1337 // get number of rows
1338 if(stat_secrets_total)
1340 if(stat_monsters_total)
1343 // if no rows, return
1347 // draw table header
1348 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1349 pos.y += 1.25 * hud_fontsize.y;
1350 if(panel.current_panel_bg != "0")
1351 pos.y += panel_bg_border;
1354 panel_size.y = hud_fontsize.y * rows;
1355 panel_size.y += panel_bg_padding * 2;
1358 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1359 if(panel.current_panel_bg != "0")
1360 end_pos.y += panel_bg_border * 2;
1362 if(panel_bg_padding)
1364 panel_pos += '1 1 0' * panel_bg_padding;
1365 panel_size -= '2 2 0' * panel_bg_padding;
1369 vector tmp = panel_size;
1372 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1375 if(stat_monsters_total)
1377 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1378 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1382 if(stat_secrets_total)
1384 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1385 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1388 panel_size.x += panel_bg_padding * 2; // restore initial width
1393 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1396 RANKINGS_RECEIVED_CNT = 0;
1397 for (i=RANKINGS_CNT-1; i>=0; --i)
1399 ++RANKINGS_RECEIVED_CNT;
1401 if (RANKINGS_RECEIVED_CNT == 0)
1404 vector hl_rgb = rgb + '0.5 0.5 0.5';
1406 pos.y += hud_fontsize.y;
1407 drawstring(pos + eX * panel_bg_padding, ((ISGAMETYPE(CTF)) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1408 pos.y += 1.25 * hud_fontsize.y;
1409 if(panel.current_panel_bg != "0")
1410 pos.y += panel_bg_border;
1415 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1417 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1422 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1424 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1428 float ranksize = 3 * hud_fontsize.x;
1429 float timesize = 5 * hud_fontsize.x;
1430 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1431 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1432 columns = min(columns, RANKINGS_RECEIVED_CNT);
1434 // expand name column to fill the entire row
1435 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1436 namesize += available_space;
1437 columnsize.x += available_space;
1439 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1440 panel_size.y += panel_bg_padding * 2;
1444 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1445 if(panel.current_panel_bg != "0")
1446 end_pos.y += panel_bg_border * 2;
1448 if(panel_bg_padding)
1450 panel_pos += '1 1 0' * panel_bg_padding;
1451 panel_size -= '2 2 0' * panel_bg_padding;
1457 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1459 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1461 int column = 0, j = 0;
1462 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1469 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1470 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1471 else if(!((j + column) & 1) && sbt_highlight)
1472 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1474 str = count_ordinal(i+1);
1475 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1476 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1477 str = ColorTranslateRGB(grecordholder[i]);
1479 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1480 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1482 pos.y += 1.25 * hud_fontsize.y;
1484 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1488 pos.x += panel_size.x / columns;
1489 pos.y = panel_pos.y;
1493 panel_size.x += panel_bg_padding * 2; // restore initial width
1497 float scoreboard_time;
1498 bool have_weapon_stats;
1499 bool Scoreboard_AccuracyStats_WouldDraw(float ypos)
1501 if (ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || ISGAMETYPE(NEXBALL))
1503 if (!autocvar_hud_panel_scoreboard_accuracy || warmup_stage || ypos > 0.91 * vid_conheight)
1506 if (time < scoreboard_time + autocvar_hud_panel_scoreboard_accuracy_showdelay
1507 && ypos > autocvar_hud_panel_scoreboard_accuracy_showdelay_minpos * vid_conheight
1513 if (!have_weapon_stats)
1515 FOREACH(Weapons, it != WEP_Null, {
1516 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1517 if (weapon_stats >= 0)
1519 have_weapon_stats = true;
1523 if (!have_weapon_stats)
1530 void Scoreboard_Draw()
1532 if(!autocvar__hud_configure)
1534 if(!hud_draw_maximized) return;
1536 // frametime checks allow to toggle the scoreboard even when the game is paused
1537 if(scoreboard_active) {
1538 if (scoreboard_fade_alpha < 1)
1539 scoreboard_time = time;
1540 if(hud_configure_menu_open == 1)
1541 scoreboard_fade_alpha = 1;
1542 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1543 if (scoreboard_fadeinspeed && frametime)
1544 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1546 scoreboard_fade_alpha = 1;
1547 if(hud_fontsize_str != autocvar_hud_fontsize)
1549 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1550 Scoreboard_initFieldSizes();
1551 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1555 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1556 if (scoreboard_fadeoutspeed && frametime)
1557 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1559 scoreboard_fade_alpha = 0;
1562 if (!scoreboard_fade_alpha)
1564 scoreboard_acc_fade_alpha = 0;
1569 scoreboard_fade_alpha = 0;
1571 if (autocvar_hud_panel_scoreboard_dynamichud)
1574 HUD_Scale_Disable();
1576 if(scoreboard_fade_alpha <= 0)
1578 panel_fade_alpha *= scoreboard_fade_alpha;
1579 HUD_Panel_LoadCvars();
1581 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1582 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1583 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1584 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1585 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1586 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1588 // don't overlap with con_notify
1589 if(!autocvar__hud_configure)
1590 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1592 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1593 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1594 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1595 panel_size.x = fixed_scoreboard_width;
1597 Scoreboard_UpdatePlayerTeams();
1599 vector pos = panel_pos;
1605 vector sb_heading_fontsize;
1606 sb_heading_fontsize = hud_fontsize * 2;
1607 draw_beginBoldFont();
1608 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1611 pos.y += sb_heading_fontsize.y;
1612 if(panel.current_panel_bg != "0")
1613 pos.y += panel_bg_border;
1615 // Draw the scoreboard
1616 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1619 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1623 vector panel_bg_color_save = panel_bg_color;
1624 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1625 if(panel.current_panel_bg != "0")
1626 team_score_baseoffset.x -= panel_bg_border;
1627 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1629 if(tm.team == NUM_SPECTATOR)
1634 draw_beginBoldFont();
1635 vector rgb = Team_ColorRGB(tm.team);
1636 str = ftos(tm.(teamscores(ts_primary)));
1637 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1638 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1640 if(ts_primary != ts_secondary)
1642 str = ftos(tm.(teamscores(ts_secondary)));
1643 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1644 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1647 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1648 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1649 else if(panel_bg_color_team > 0)
1650 panel_bg_color = rgb * panel_bg_color_team;
1652 panel_bg_color = rgb;
1653 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1655 panel_bg_color = panel_bg_color_save;
1659 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1660 if(tm.team != NUM_SPECTATOR)
1662 // display it anyway
1663 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1666 if (Scoreboard_AccuracyStats_WouldDraw(pos.y))
1667 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1669 if(ISGAMETYPE(CTS) || ISGAMETYPE(RACE) || (autocvar_hud_panel_scoreboard_ctf_leaderboard && ISGAMETYPE(CTF) && STAT(CTF_SHOWLEADERBOARD))) {
1670 if(race_speedaward) {
1671 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);
1672 pos.y += 1.25 * hud_fontsize.y;
1674 if(race_speedaward_alltimebest) {
1675 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);
1676 pos.y += 1.25 * hud_fontsize.y;
1678 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1681 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1684 for(pl = players.sort_next; pl; pl = pl.sort_next)
1686 if(pl.team == NUM_SPECTATOR)
1688 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1689 if(tm.team == NUM_SPECTATOR)
1691 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1692 draw_beginBoldFont();
1693 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1695 pos.y += 1.25 * hud_fontsize.y;
1697 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1698 pos.y += 1.25 * hud_fontsize.y;
1704 // Print info string
1706 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1707 tl = STAT(TIMELIMIT);
1708 fl = STAT(FRAGLIMIT);
1709 ll = STAT(LEADLIMIT);
1713 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1718 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1722 str = strcat(str, _(" or"));
1725 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1726 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1727 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1728 TranslateScoresLabel(teamscores_label(ts_primary))));
1732 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1733 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1734 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1735 TranslateScoresLabel(scores_label(ps_primary))));
1740 if(tl > 0 || fl > 0)
1741 str = strcat(str, _(" or"));
1744 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1745 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1746 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1747 TranslateScoresLabel(teamscores_label(ts_primary))));
1751 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1752 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1753 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1754 TranslateScoresLabel(scores_label(ps_primary))));
1759 pos.y += 1.2 * hud_fontsize.y;
1760 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1762 // print information about respawn status
1763 float respawn_time = STAT(RESPAWN_TIME);
1767 if(respawn_time < 0)
1769 // a negative number means we are awaiting respawn, time value is still the same
1770 respawn_time *= -1; // remove mark now that we checked it
1772 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1773 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1775 str = sprintf(_("^1Respawning in ^3%s^1..."),
1776 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1777 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1779 count_seconds(ceil(respawn_time - time))
1783 else if(time < respawn_time)
1785 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1786 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1787 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1789 count_seconds(ceil(respawn_time - time))
1793 else if(time >= respawn_time)
1794 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1796 pos.y += 1.2 * hud_fontsize.y;
1797 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1800 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;