1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/miscfunctions.qh>
6 #include "quickmenu.qh"
7 #include <common/ent_cs.qh>
8 #include <common/constants.qh>
9 #include <common/net_linked.qh>
10 #include <common/mapinfo.qh>
11 #include <common/minigames/cl_minigames.qh>
12 #include <common/scores.qh>
13 #include <common/stats.qh>
14 #include <common/teams.qh>
18 const int MAX_SBT_FIELDS = MAX_SCORE;
20 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
21 float sbt_field_size[MAX_SBT_FIELDS + 1];
22 string sbt_field_title[MAX_SBT_FIELDS + 1];
25 string autocvar_hud_fontsize;
26 string hud_fontsize_str;
31 float sbt_fg_alpha_self;
33 float sbt_highlight_alpha;
34 float sbt_highlight_alpha_self;
36 // provide basic panel cvars to old clients
37 // TODO remove them after a future release (0.8.2+)
38 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
39 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
40 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
41 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
42 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
43 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
44 noref string autocvar_hud_panel_scoreboard_bg_border = "";
45 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
47 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
48 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
49 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
50 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
51 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
52 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
54 bool autocvar_hud_panel_scoreboard_table_highlight = true;
55 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
57 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
58 float autocvar_hud_panel_scoreboard_namesize = 15;
60 bool autocvar_hud_panel_scoreboard_accuracy = true;
61 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
62 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
63 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
65 bool autocvar_hud_panel_scoreboard_dynamichud = false;
67 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
68 bool autocvar_hud_panel_scoreboard_others_showscore = true;
69 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
70 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
71 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 void drawstringright(vector, string, vector, vector, float, float);
75 void drawstringcenter(vector, string, vector, vector, float, float);
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 "laps": return CTX(_("SCO^laps"));
100 case "lives": return CTX(_("SCO^lives"));
101 case "losses": return CTX(_("SCO^losses"));
102 case "name": return CTX(_("SCO^name"));
103 case "sum": return CTX(_("SCO^sum"));
104 case "nick": return CTX(_("SCO^nick"));
105 case "objectives": return CTX(_("SCO^objectives"));
106 case "pickups": return CTX(_("SCO^pickups"));
107 case "ping": return CTX(_("SCO^ping"));
108 case "pl": return CTX(_("SCO^pl"));
109 case "pushes": return CTX(_("SCO^pushes"));
110 case "rank": return CTX(_("SCO^rank"));
111 case "returns": return CTX(_("SCO^returns"));
112 case "revivals": return CTX(_("SCO^revivals"));
113 case "rounds": return CTX(_("SCO^rounds won"));
114 case "score": return CTX(_("SCO^score"));
115 case "suicides": return CTX(_("SCO^suicides"));
116 case "takes": return CTX(_("SCO^takes"));
117 case "ticks": return CTX(_("SCO^ticks"));
122 void Scoreboard_InitScores()
126 ps_primary = ps_secondary = NULL;
127 ts_primary = ts_secondary = -1;
128 FOREACH(Scores, true, {
129 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
130 if(f == SFL_SORT_PRIO_PRIMARY)
132 if(f == SFL_SORT_PRIO_SECONDARY)
135 if(ps_secondary == NULL)
136 ps_secondary = ps_primary;
138 for(i = 0; i < MAX_TEAMSCORE; ++i)
140 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
141 if(f == SFL_SORT_PRIO_PRIMARY)
143 if(f == SFL_SORT_PRIO_SECONDARY)
146 if(ts_secondary == -1)
147 ts_secondary = ts_primary;
149 Cmd_Scoreboard_SetFields(0);
152 float SetTeam(entity pl, float Team);
154 void Scoreboard_UpdatePlayerTeams()
159 for(pl = players.sort_next; pl; pl = pl.sort_next)
162 Team = entcs_GetScoreTeam(pl.sv_entnum);
163 if(SetTeam(pl, Team))
166 Scoreboard_UpdatePlayerPos(pl);
170 pl = players.sort_next;
175 print(strcat("PNUM: ", ftos(num), "\n"));
180 int Scoreboard_CompareScore(int vl, int vr, int f)
182 TC(int, vl); TC(int, vr); TC(int, f);
183 if(f & SFL_ZERO_IS_WORST)
185 if(vl == 0 && vr != 0)
187 if(vl != 0 && vr == 0)
191 return IS_INCREASING(f);
193 return IS_DECREASING(f);
197 float Scoreboard_ComparePlayerScores(entity left, entity right)
200 vl = entcs_GetTeam(left.sv_entnum);
201 vr = entcs_GetTeam(right.sv_entnum);
213 if(vl == NUM_SPECTATOR)
215 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
217 if(!left.gotscores && right.gotscores)
222 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
226 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
230 FOREACH(Scores, true, {
231 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
232 if (r >= 0) return r;
235 if (left.sv_entnum < right.sv_entnum)
241 void Scoreboard_UpdatePlayerPos(entity player)
244 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
246 SORT_SWAP(player, ent);
248 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
250 SORT_SWAP(ent, player);
254 float Scoreboard_CompareTeamScores(entity left, entity right)
258 if(left.team == NUM_SPECTATOR)
260 if(right.team == NUM_SPECTATOR)
263 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
267 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
271 for(i = 0; i < MAX_TEAMSCORE; ++i)
273 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
278 if (left.team < right.team)
284 void Scoreboard_UpdateTeamPos(entity Team)
287 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
289 SORT_SWAP(Team, ent);
291 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
293 SORT_SWAP(ent, Team);
297 void Cmd_Scoreboard_Help()
299 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
300 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
301 LOG_INFO(_("Usage:\n"));
302 LOG_INFO(_("^2scoreboard_columns_set default\n"));
303 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
304 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
305 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
308 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
309 LOG_INFO(_("^3ping^7 Ping time\n"));
310 LOG_INFO(_("^3pl^7 Packet loss\n"));
311 LOG_INFO(_("^3elo^7 Player ELO\n"));
312 LOG_INFO(_("^3kills^7 Number of kills\n"));
313 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
314 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
315 LOG_INFO(_("^3frags^7 kills - suicides\n"));
316 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
317 LOG_INFO(_("^3dmg^7 The total damage done\n"));
318 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
319 LOG_INFO(_("^3sum^7 frags - deaths\n"));
320 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
321 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
322 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
323 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
324 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
325 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
326 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
327 LOG_INFO(_("^3rank^7 Player rank\n"));
328 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
329 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
330 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
331 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
332 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
333 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
334 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
335 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
336 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
337 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
338 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
339 LOG_INFO(_("^3score^7 Total score\n"));
342 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
343 "of game types, then a slash, to make the field show up only in these\n"
344 "or in all but these game types. You can also specify 'all' as a\n"
345 "field to show all fields available for the current game mode.\n\n"));
347 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
348 "include/exclude ALL teams/noteams game modes.\n\n"));
350 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
351 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
352 "right of the vertical bar aligned to the right.\n"));
353 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
354 "other gamemodes except DM.\n"));
357 // NOTE: adding a gametype with ? to not warn for an optional field
358 // make sure it's excluded in a previous exclusive rule, if any
359 // otherwise the previous exclusive rule warns anyway
360 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
361 #define SCOREBOARD_DEFAULT_COLUMNS \
363 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
364 " -teams,lms/deaths +ft,tdm/deaths" \
365 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
366 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
367 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
368 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
369 " +lms/lives +lms/rank" \
370 " +kh/caps +kh/pushes +kh/destroyed" \
371 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
372 " +as/objectives +nb/faults +nb/goals" \
373 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
374 " -lms,rc,cts,inv,nb/score"
376 void Cmd_Scoreboard_SetFields(int argc)
381 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
385 return; // do nothing, we don't know gametype and scores yet
387 // sbt_fields uses strunzone on the titles!
388 if(!sbt_field_title[0])
389 for(i = 0; i < MAX_SBT_FIELDS; ++i)
390 sbt_field_title[i] = strzone("(null)");
392 // TODO: re enable with gametype dependant cvars?
393 if(argc < 3) // no arguments provided
394 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
397 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
401 if(argv(2) == "default")
402 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
403 else if(argv(2) == "all")
406 s = "ping pl name |";
407 FOREACH(Scores, true, {
409 if(it != ps_secondary)
410 if(scores_label(it) != "")
411 s = strcat(s, " ", scores_label(it));
413 if(ps_secondary != ps_primary)
414 s = strcat(s, " ", scores_label(ps_secondary));
415 s = strcat(s, " ", scores_label(ps_primary));
416 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
423 hud_fontsize = HUD_GetFontsize("hud_fontsize");
425 for(i = 1; i < argc - 1; ++i)
431 if(substring(str, 0, 1) == "?")
434 str = substring(str, 1, strlen(str) - 1);
437 slash = strstrofs(str, "/", 0);
440 pattern = substring(str, 0, slash);
441 str = substring(str, slash + 1, strlen(str) - (slash + 1));
443 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
447 strunzone(sbt_field_title[sbt_num_fields]);
448 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
449 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
450 str = strtolower(str);
455 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
456 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
457 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
458 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
459 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
460 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
461 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
462 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
463 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
466 FOREACH(Scores, true, {
467 if (str == strtolower(scores_label(it))) {
469 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
479 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
483 sbt_field[sbt_num_fields] = j;
486 if(j == ps_secondary)
487 have_secondary = true;
492 if(sbt_num_fields >= MAX_SBT_FIELDS)
496 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
498 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
499 have_secondary = true;
500 if(ps_primary == ps_secondary)
501 have_secondary = true;
502 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
504 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
508 strunzone(sbt_field_title[sbt_num_fields]);
509 for(i = sbt_num_fields; i > 0; --i)
511 sbt_field_title[i] = sbt_field_title[i-1];
512 sbt_field_size[i] = sbt_field_size[i-1];
513 sbt_field[i] = sbt_field[i-1];
515 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
516 sbt_field[0] = SP_NAME;
518 LOG_INFO("fixed missing field 'name'\n");
522 strunzone(sbt_field_title[sbt_num_fields]);
523 for(i = sbt_num_fields; i > 1; --i)
525 sbt_field_title[i] = sbt_field_title[i-1];
526 sbt_field_size[i] = sbt_field_size[i-1];
527 sbt_field[i] = sbt_field[i-1];
529 sbt_field_title[1] = strzone("|");
530 sbt_field[1] = SP_SEPARATOR;
531 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
533 LOG_INFO("fixed missing field '|'\n");
536 else if(!have_separator)
538 strunzone(sbt_field_title[sbt_num_fields]);
539 sbt_field_title[sbt_num_fields] = strzone("|");
540 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
541 sbt_field[sbt_num_fields] = SP_SEPARATOR;
543 LOG_INFO("fixed missing field '|'\n");
547 strunzone(sbt_field_title[sbt_num_fields]);
548 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
549 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
550 sbt_field[sbt_num_fields] = ps_secondary;
552 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
556 strunzone(sbt_field_title[sbt_num_fields]);
557 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
558 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
559 sbt_field[sbt_num_fields] = ps_primary;
561 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
565 sbt_field[sbt_num_fields] = SP_END;
569 vector sbt_field_rgb;
570 string sbt_field_icon0;
571 string sbt_field_icon1;
572 string sbt_field_icon2;
573 vector sbt_field_icon0_rgb;
574 vector sbt_field_icon1_rgb;
575 vector sbt_field_icon2_rgb;
576 string Scoreboard_GetName(entity pl)
578 if(ready_waiting && pl.ready)
580 sbt_field_icon0 = "gfx/scoreboard/player_ready";
584 int f = entcs_GetClientColors(pl.sv_entnum);
586 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
587 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
588 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
589 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
590 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
593 return entcs_GetName(pl.sv_entnum);
595 string Scoreboard_GetField(entity pl, PlayerScoreField field)
597 float tmp, num, denom;
600 sbt_field_rgb = '1 1 1';
601 sbt_field_icon0 = "";
602 sbt_field_icon1 = "";
603 sbt_field_icon2 = "";
604 sbt_field_icon0_rgb = '1 1 1';
605 sbt_field_icon1_rgb = '1 1 1';
606 sbt_field_icon2_rgb = '1 1 1';
611 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
612 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
616 tmp = max(0, min(220, f-80)) / 220;
617 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
623 f = pl.ping_packetloss;
624 tmp = pl.ping_movementloss;
625 if(f == 0 && tmp == 0)
627 str = ftos(ceil(f * 100));
629 str = strcat(str, "~", ftos(ceil(tmp * 100)));
630 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
631 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
635 return Scoreboard_GetName(pl);
638 f = pl.(scores(SP_KILLS));
639 f -= pl.(scores(SP_SUICIDES));
643 num = pl.(scores(SP_KILLS));
644 denom = pl.(scores(SP_DEATHS));
647 sbt_field_rgb = '0 1 0';
648 str = sprintf("%d", num);
649 } else if(num <= 0) {
650 sbt_field_rgb = '1 0 0';
651 str = sprintf("%.1f", num/denom);
653 str = sprintf("%.1f", num/denom);
657 f = pl.(scores(SP_KILLS));
658 f -= pl.(scores(SP_DEATHS));
661 sbt_field_rgb = '0 1 0';
663 sbt_field_rgb = '1 1 1';
665 sbt_field_rgb = '1 0 0';
671 float elo = pl.(scores(SP_ELO));
673 case -1: return "...";
674 case -2: return _("N/A");
675 default: return ftos(elo);
679 case SP_DMG: case SP_DMGTAKEN:
680 return sprintf("%.1f k", pl.(scores(field)) / 1000);
682 default: case SP_SCORE:
683 tmp = pl.(scores(field));
684 f = scores_flags(field);
685 if(field == ps_primary)
686 sbt_field_rgb = '1 1 0';
687 else if(field == ps_secondary)
688 sbt_field_rgb = '0 1 1';
690 sbt_field_rgb = '1 1 1';
691 return ScoreString(f, tmp);
696 float sbt_fixcolumnwidth_len;
697 float sbt_fixcolumnwidth_iconlen;
698 float sbt_fixcolumnwidth_marginlen;
700 string Scoreboard_FixColumnWidth(int i, string str)
706 sbt_fixcolumnwidth_iconlen = 0;
708 if(sbt_field_icon0 != "")
710 sz = draw_getimagesize(sbt_field_icon0);
712 if(sbt_fixcolumnwidth_iconlen < f)
713 sbt_fixcolumnwidth_iconlen = f;
716 if(sbt_field_icon1 != "")
718 sz = draw_getimagesize(sbt_field_icon1);
720 if(sbt_fixcolumnwidth_iconlen < f)
721 sbt_fixcolumnwidth_iconlen = f;
724 if(sbt_field_icon2 != "")
726 sz = draw_getimagesize(sbt_field_icon2);
728 if(sbt_fixcolumnwidth_iconlen < f)
729 sbt_fixcolumnwidth_iconlen = f;
732 if(sbt_fixcolumnwidth_iconlen != 0)
734 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
735 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
738 sbt_fixcolumnwidth_marginlen = 0;
740 if(sbt_field[i] == SP_NAME) // name gets all remaining space
743 float remaining_space = 0;
744 for(j = 0; j < sbt_num_fields; ++j)
746 if (sbt_field[i] != SP_SEPARATOR)
747 remaining_space += sbt_field_size[j] + hud_fontsize.x;
748 sbt_field_size[i] = panel_size.x - remaining_space;
750 if (sbt_fixcolumnwidth_iconlen != 0)
751 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
752 float namesize = panel_size.x - remaining_space;
753 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
754 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
756 max_namesize = vid_conwidth - remaining_space;
759 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
761 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
762 if(sbt_field_size[i] < f)
763 sbt_field_size[i] = f;
768 void Scoreboard_initFieldSizes()
770 for(int i = 0; i < sbt_num_fields; ++i)
772 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
773 Scoreboard_FixColumnWidth(i, "");
777 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
780 vector column_dim = eY * panel_size.y;
782 column_dim.y -= 1.25 * hud_fontsize.y;
783 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
784 pos.x += hud_fontsize.x * 0.5;
785 for(i = 0; i < sbt_num_fields; ++i)
787 if(sbt_field[i] == SP_SEPARATOR)
789 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
792 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
793 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
794 pos.x += column_dim.x;
796 if(sbt_field[i] == SP_SEPARATOR)
798 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
799 for(i = sbt_num_fields - 1; i > 0; --i)
801 if(sbt_field[i] == SP_SEPARATOR)
804 pos.x -= sbt_field_size[i];
809 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
810 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
813 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
814 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
815 pos.x -= hud_fontsize.x;
820 pos.y += 1.25 * hud_fontsize.y;
824 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
826 TC(bool, is_self); TC(int, pl_number);
828 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
830 vector h_pos = item_pos;
831 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
832 // alternated rows highlighting
834 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
835 else if((sbt_highlight) && (!(pl_number % 2)))
836 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
838 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
840 vector pos = item_pos;
841 pos.x += hud_fontsize.x * 0.5;
842 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
843 vector tmp = '0 0 0';
845 PlayerScoreField field;
846 for(i = 0; i < sbt_num_fields; ++i)
848 field = sbt_field[i];
849 if(field == SP_SEPARATOR)
852 if(is_spec && field != SP_NAME && field != SP_PING) {
853 pos.x += sbt_field_size[i] + hud_fontsize.x;
856 str = Scoreboard_GetField(pl, field);
857 str = Scoreboard_FixColumnWidth(i, str);
859 pos.x += sbt_field_size[i] + hud_fontsize.x;
861 if(field == SP_NAME) {
862 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
863 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
865 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
866 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
869 tmp.x = sbt_field_size[i] + hud_fontsize.x;
870 if(sbt_field_icon0 != "")
871 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
872 if(sbt_field_icon1 != "")
873 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
874 if(sbt_field_icon2 != "")
875 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
878 if(sbt_field[i] == SP_SEPARATOR)
880 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
881 for(i = sbt_num_fields-1; i > 0; --i)
883 field = sbt_field[i];
884 if(field == SP_SEPARATOR)
887 if(is_spec && field != SP_NAME && field != SP_PING) {
888 pos.x -= sbt_field_size[i] + hud_fontsize.x;
892 str = Scoreboard_GetField(pl, field);
893 str = Scoreboard_FixColumnWidth(i, str);
895 if(field == SP_NAME) {
896 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
897 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
899 tmp.x = sbt_fixcolumnwidth_len;
900 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
903 tmp.x = sbt_field_size[i];
904 if(sbt_field_icon0 != "")
905 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
906 if(sbt_field_icon1 != "")
907 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
908 if(sbt_field_icon2 != "")
909 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
910 pos.x -= sbt_field_size[i] + hud_fontsize.x;
915 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
918 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
921 vector h_pos = item_pos;
922 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
924 bool complete = (this_team == NUM_SPECTATOR);
927 if((sbt_highlight) && (!(pl_number % 2)))
928 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
930 vector pos = item_pos;
931 pos.x += hud_fontsize.x * 0.5;
932 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
934 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
936 width_limit -= stringwidth("...", false, hud_fontsize);
937 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
938 static float max_name_width = 0;
941 float min_fieldsize = 0;
942 float fieldpadding = hud_fontsize.x * 0.25;
943 if(this_team == NUM_SPECTATOR)
945 if(autocvar_hud_panel_scoreboard_spectators_showping)
946 min_fieldsize = stringwidth("999", false, hud_fontsize);
948 else if(autocvar_hud_panel_scoreboard_others_showscore)
949 min_fieldsize = stringwidth("99", false, hud_fontsize);
950 for(i = 0; pl; pl = pl.sort_next)
952 if(pl.team != this_team)
958 if(this_team == NUM_SPECTATOR)
960 if(autocvar_hud_panel_scoreboard_spectators_showping)
961 field = Scoreboard_GetField(pl, SP_PING);
963 else if(autocvar_hud_panel_scoreboard_others_showscore)
964 field = Scoreboard_GetField(pl, SP_SCORE);
966 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
967 float column_width = stringwidth(str, true, hud_fontsize);
968 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
970 if(column_width > max_name_width)
971 max_name_width = column_width;
972 column_width = max_name_width;
976 fieldsize = stringwidth(field, false, hud_fontsize);
977 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
980 if(pos.x + column_width > width_limit)
985 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
990 pos.x = item_pos.x + hud_fontsize.x * 0.5;
991 pos.y += hud_fontsize.y * 1.25;
995 vector name_pos = pos;
996 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
997 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
998 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1001 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1002 h_size.y = hud_fontsize.y;
1003 vector field_pos = pos;
1004 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1005 field_pos.x += column_width - h_size.x;
1007 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1008 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1009 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1011 pos.x += column_width;
1012 pos.x += hud_fontsize.x;
1014 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1017 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1019 int max_players = 999;
1020 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1022 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1025 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1026 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1027 height /= team_count;
1030 height -= panel_bg_padding * 2; // - padding
1031 max_players = floor(height / (hud_fontsize.y * 1.25));
1032 if(max_players <= 1)
1034 if(max_players == tm.team_size)
1039 entity me = playerslots[current_player];
1041 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1042 panel_size.y += panel_bg_padding * 2;
1045 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1046 if(panel.current_panel_bg != "0")
1047 end_pos.y += panel_bg_border * 2;
1049 if(panel_bg_padding)
1051 panel_pos += '1 1 0' * panel_bg_padding;
1052 panel_size -= '2 2 0' * panel_bg_padding;
1056 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1060 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1062 pos.y += 1.25 * hud_fontsize.y;
1065 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1067 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1070 // print header row and highlight columns
1071 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1073 // fill the table and draw the rows
1074 bool is_self = false;
1075 bool self_shown = false;
1077 for(pl = players.sort_next; pl; pl = pl.sort_next)
1079 if(pl.team != tm.team)
1081 if(i == max_players - 2 && pl != me)
1083 if(!self_shown && me.team == tm.team)
1085 Scoreboard_DrawItem(pos, rgb, me, true, i);
1087 pos.y += 1.25 * hud_fontsize.y;
1091 if(i >= max_players - 1)
1093 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1096 is_self = (pl.sv_entnum == current_player);
1097 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1100 pos.y += 1.25 * hud_fontsize.y;
1104 panel_size.x += panel_bg_padding * 2; // restore initial width
1108 bool Scoreboard_WouldDraw()
1110 if (MUTATOR_CALLHOOK(DrawScoreboard))
1112 else if (QuickMenu_IsOpened())
1114 else if (HUD_Radar_Clickable())
1116 else if (scoreboard_showscores)
1118 else if (intermission == 1)
1120 else if (intermission == 2)
1122 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1124 else if (scoreboard_showscores_force)
1129 float average_accuracy;
1130 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1132 WepSet weapons_stat = WepSet_GetFromStat();
1133 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1134 int disownedcnt = 0;
1136 FOREACH(Weapons, it != WEP_Null, {
1137 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1139 WepSet set = it.m_wepset;
1140 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1142 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1149 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1150 if (weapon_cnt <= 0) return pos;
1153 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1155 int columnns = ceil(weapon_cnt / rows);
1157 float weapon_height = 29;
1158 float height = hud_fontsize.y + weapon_height;
1160 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1161 pos.y += 1.25 * hud_fontsize.y;
1162 if(panel.current_panel_bg != "0")
1163 pos.y += panel_bg_border;
1166 panel_size.y = height * rows;
1167 panel_size.y += panel_bg_padding * 2;
1170 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1171 if(panel.current_panel_bg != "0")
1172 end_pos.y += panel_bg_border * 2;
1174 if(panel_bg_padding)
1176 panel_pos += '1 1 0' * panel_bg_padding;
1177 panel_size -= '2 2 0' * panel_bg_padding;
1181 vector tmp = panel_size;
1183 float weapon_width = tmp.x / columnns / rows;
1186 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1190 // column highlighting
1191 for (int i = 0; i < columnns; ++i)
1193 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1196 for (int i = 0; i < rows; ++i)
1197 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1200 average_accuracy = 0;
1201 int weapons_with_stats = 0;
1203 pos.x += weapon_width / 2;
1205 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1208 Accuracy_LoadColors();
1210 float oldposx = pos.x;
1214 FOREACH(Weapons, it != WEP_Null, {
1215 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1217 WepSet set = it.m_wepset;
1218 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1222 if (weapon_stats >= 0)
1223 weapon_alpha = sbt_fg_alpha;
1225 weapon_alpha = 0.2 * sbt_fg_alpha;
1228 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1230 if (weapon_stats >= 0) {
1231 weapons_with_stats += 1;
1232 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1235 s = sprintf("%d%%", weapon_stats * 100);
1238 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1240 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1241 rgb = Accuracy_GetColor(weapon_stats);
1243 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1245 tmpos.x += weapon_width * rows;
1246 pos.x += weapon_width * rows;
1247 if (rows == 2 && column == columnns - 1) {
1255 if (weapons_with_stats)
1256 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1258 panel_size.x += panel_bg_padding * 2; // restore initial width
1262 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1264 pos.x += hud_fontsize.x * 0.25;
1265 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1266 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1267 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1269 pos.y += hud_fontsize.y;
1274 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1275 float stat_secrets_found, stat_secrets_total;
1276 float stat_monsters_killed, stat_monsters_total;
1280 // get monster stats
1281 stat_monsters_killed = STAT(MONSTERS_KILLED);
1282 stat_monsters_total = STAT(MONSTERS_TOTAL);
1284 // get secrets stats
1285 stat_secrets_found = STAT(SECRETS_FOUND);
1286 stat_secrets_total = STAT(SECRETS_TOTAL);
1288 // get number of rows
1289 if(stat_secrets_total)
1291 if(stat_monsters_total)
1294 // if no rows, return
1298 // draw table header
1299 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1300 pos.y += 1.25 * hud_fontsize.y;
1301 if(panel.current_panel_bg != "0")
1302 pos.y += panel_bg_border;
1305 panel_size.y = hud_fontsize.y * rows;
1306 panel_size.y += panel_bg_padding * 2;
1309 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1310 if(panel.current_panel_bg != "0")
1311 end_pos.y += panel_bg_border * 2;
1313 if(panel_bg_padding)
1315 panel_pos += '1 1 0' * panel_bg_padding;
1316 panel_size -= '2 2 0' * panel_bg_padding;
1320 vector tmp = panel_size;
1323 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1326 if(stat_monsters_total)
1328 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1329 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1333 if(stat_secrets_total)
1335 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1336 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1339 panel_size.x += panel_bg_padding * 2; // restore initial width
1344 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1347 RANKINGS_RECEIVED_CNT = 0;
1348 for (i=RANKINGS_CNT-1; i>=0; --i)
1350 ++RANKINGS_RECEIVED_CNT;
1352 if (RANKINGS_RECEIVED_CNT == 0)
1355 vector hl_rgb = rgb + '0.5 0.5 0.5';
1357 pos.y += hud_fontsize.y;
1358 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1359 pos.y += 1.25 * hud_fontsize.y;
1360 if(panel.current_panel_bg != "0")
1361 pos.y += panel_bg_border;
1366 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1368 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1373 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1375 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1379 float ranksize = 3 * hud_fontsize.x;
1380 float timesize = 5 * hud_fontsize.x;
1381 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1382 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1383 columns = min(columns, RANKINGS_RECEIVED_CNT);
1385 // expand name column to fill the entire row
1386 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1387 namesize += available_space;
1388 columnsize.x += available_space;
1390 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1391 panel_size.y += panel_bg_padding * 2;
1395 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1396 if(panel.current_panel_bg != "0")
1397 end_pos.y += panel_bg_border * 2;
1399 if(panel_bg_padding)
1401 panel_pos += '1 1 0' * panel_bg_padding;
1402 panel_size -= '2 2 0' * panel_bg_padding;
1408 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1410 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1412 int column = 0, j = 0;
1413 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1420 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1421 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1422 else if(!((j + column) & 1) && sbt_highlight)
1423 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1425 str = count_ordinal(i+1);
1426 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1427 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1428 str = ColorTranslateRGB(grecordholder[i]);
1430 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1431 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1433 pos.y += 1.25 * hud_fontsize.y;
1435 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1439 pos.x += panel_size.x / columns;
1440 pos.y = panel_pos.y;
1444 panel_size.x += panel_bg_padding * 2; // restore initial width
1448 void Scoreboard_Draw()
1450 if(!autocvar__hud_configure)
1452 if(!hud_draw_maximized) return;
1454 // frametime checks allow to toggle the scoreboard even when the game is paused
1455 if(scoreboard_active) {
1456 if(hud_configure_menu_open == 1)
1457 scoreboard_fade_alpha = 1;
1458 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1459 if (scoreboard_fadeinspeed && frametime)
1460 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1462 scoreboard_fade_alpha = 1;
1463 if(hud_fontsize_str != autocvar_hud_fontsize)
1465 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1466 Scoreboard_initFieldSizes();
1467 if(hud_fontsize_str)
1468 strunzone(hud_fontsize_str);
1469 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1473 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1474 if (scoreboard_fadeoutspeed && frametime)
1475 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1477 scoreboard_fade_alpha = 0;
1480 if (!scoreboard_fade_alpha)
1484 scoreboard_fade_alpha = 0;
1486 if (autocvar_hud_panel_scoreboard_dynamichud)
1489 HUD_Scale_Disable();
1491 if(scoreboard_fade_alpha <= 0)
1493 panel_fade_alpha *= scoreboard_fade_alpha;
1494 HUD_Panel_LoadCvars();
1496 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1497 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1498 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1499 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1500 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1501 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1503 // don't overlap with con_notify
1504 if(!autocvar__hud_configure)
1505 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1507 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1508 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1509 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1510 panel_size.x = fixed_scoreboard_width;
1512 Scoreboard_UpdatePlayerTeams();
1514 vector pos = panel_pos;
1520 vector sb_heading_fontsize;
1521 sb_heading_fontsize = hud_fontsize * 2;
1522 draw_beginBoldFont();
1523 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1526 pos.y += sb_heading_fontsize.y;
1527 if(panel.current_panel_bg != "0")
1528 pos.y += panel_bg_border;
1530 // Draw the scoreboard
1531 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1534 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1538 vector panel_bg_color_save = panel_bg_color;
1539 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1540 if(panel.current_panel_bg != "0")
1541 team_score_baseoffset.x -= panel_bg_border;
1542 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1544 if(tm.team == NUM_SPECTATOR)
1549 draw_beginBoldFont();
1550 vector rgb = Team_ColorRGB(tm.team);
1551 str = ftos(tm.(teamscores(ts_primary)));
1552 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1553 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1555 if(ts_primary != ts_secondary)
1557 str = ftos(tm.(teamscores(ts_secondary)));
1558 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1559 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1562 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1563 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1564 else if(panel_bg_color_team > 0)
1565 panel_bg_color = rgb * panel_bg_color_team;
1567 panel_bg_color = rgb;
1568 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1570 panel_bg_color = panel_bg_color_save;
1574 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1575 if(tm.team != NUM_SPECTATOR)
1577 // display it anyway
1578 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1581 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1583 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1584 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1586 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1587 if(race_speedaward) {
1588 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);
1589 pos.y += 1.25 * hud_fontsize.y;
1591 if(race_speedaward_alltimebest) {
1592 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);
1593 pos.y += 1.25 * hud_fontsize.y;
1595 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1598 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1601 for(pl = players.sort_next; pl; pl = pl.sort_next)
1603 if(pl.team == NUM_SPECTATOR)
1605 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1606 if(tm.team == NUM_SPECTATOR)
1608 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1609 draw_beginBoldFont();
1610 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1612 pos.y += 1.25 * hud_fontsize.y;
1614 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1615 pos.y += 1.25 * hud_fontsize.y;
1621 // Print info string
1623 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1624 tl = STAT(TIMELIMIT);
1625 fl = STAT(FRAGLIMIT);
1626 ll = STAT(LEADLIMIT);
1627 if(gametype == MAPINFO_TYPE_LMS)
1630 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1635 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1639 str = strcat(str, _(" or"));
1642 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1643 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1644 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1645 TranslateScoresLabel(teamscores_label(ts_primary))));
1649 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1650 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1651 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1652 TranslateScoresLabel(scores_label(ps_primary))));
1657 if(tl > 0 || fl > 0)
1658 str = strcat(str, _(" or"));
1661 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1662 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1663 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1664 TranslateScoresLabel(teamscores_label(ts_primary))));
1668 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1669 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1670 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1671 TranslateScoresLabel(scores_label(ps_primary))));
1676 pos.y += 1.2 * hud_fontsize.y;
1677 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1679 // print information about respawn status
1680 float respawn_time = STAT(RESPAWN_TIME);
1684 if(respawn_time < 0)
1686 // a negative number means we are awaiting respawn, time value is still the same
1687 respawn_time *= -1; // remove mark now that we checked it
1689 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1690 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1692 str = sprintf(_("^1Respawning in ^3%s^1..."),
1693 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1694 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1696 count_seconds(ceil(respawn_time - time))
1700 else if(time < respawn_time)
1702 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1703 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1704 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1706 count_seconds(ceil(respawn_time - time))
1710 else if(time >= respawn_time)
1711 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1713 pos.y += 1.2 * hud_fontsize.y;
1714 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1717 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;