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 "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);
153 float SetTeam(entity pl, float Team);
155 void Scoreboard_UpdatePlayerTeams()
159 for(pl = players.sort_next; pl; pl = pl.sort_next)
162 int 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."));
300 LOG_INFO(_("^3|---------------------------------------------------------------|"));
301 LOG_INFO(_("Usage:"));
302 LOG_INFO(_("^2scoreboard_columns_set default"));
303 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
304 LOG_INFO(_("The following field names are recognized (case insensitive):"));
305 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
308 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player"));
309 LOG_INFO(_("^3ping^7 Ping time"));
310 LOG_INFO(_("^3pl^7 Packet loss"));
311 LOG_INFO(_("^3elo^7 Player ELO"));
312 LOG_INFO(_("^3fps^7 Player FPS"));
313 LOG_INFO(_("^3kills^7 Number of kills"));
314 LOG_INFO(_("^3deaths^7 Number of deaths"));
315 LOG_INFO(_("^3suicides^7 Number of suicides"));
316 LOG_INFO(_("^3frags^7 kills - suicides"));
317 LOG_INFO(_("^3teamkills^7 Number of teamkills"));
318 LOG_INFO(_("^3kd^7 The kill-death ratio"));
319 LOG_INFO(_("^3dmg^7 The total damage done"));
320 LOG_INFO(_("^3dmgtaken^7 The total damage taken"));
321 LOG_INFO(_("^3sum^7 frags - deaths"));
322 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured"));
323 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up"));
324 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)"));
325 LOG_INFO(_("^3fckills^7 Number of flag carrier kills"));
326 LOG_INFO(_("^3returns^7 Number of flag returns"));
327 LOG_INFO(_("^3drops^7 Number of flag drops"));
328 LOG_INFO(_("^3lives^7 Number of lives (LMS)"));
329 LOG_INFO(_("^3rank^7 Player rank"));
330 LOG_INFO(_("^3pushes^7 Number of players pushed into void"));
331 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void"));
332 LOG_INFO(_("^3kckills^7 Number of keys carrier kills"));
333 LOG_INFO(_("^3losses^7 Number of times a key was lost"));
334 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)"));
335 LOG_INFO(_("^3time^7 Total time raced (race/cts)"));
336 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)"));
337 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)"));
338 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)"));
339 LOG_INFO(_("^3bckills^7 Number of ball carrier kills"));
340 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway"));
341 LOG_INFO(_("^3score^7 Total score"));
344 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
345 "of game types, then a slash, to make the field show up only in these\n"
346 "or in all but these game types. You can also specify 'all' as a\n"
347 "field to show all fields available for the current game mode.\n\n"));
349 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
350 "include/exclude ALL teams/noteams game modes.\n\n"));
352 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
353 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields"
354 "right of the vertical bar aligned to the right.\n"));
355 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\nother gamemodes except DM.\n"));
358 // NOTE: adding a gametype with ? to not warn for an optional field
359 // make sure it's excluded in a previous exclusive rule, if any
360 // otherwise the previous exclusive rule warns anyway
361 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
362 #define SCOREBOARD_DEFAULT_COLUMNS \
363 "ping pl fps name |" \
364 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
365 " -teams,lms/deaths +ft,tdm/deaths" \
367 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
368 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
369 " +tdm,ft,dom,ons,as/teamkills"\
370 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
371 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
372 " +lms/lives +lms/rank" \
373 " +kh/kckills +kh/losses +kh/caps" \
374 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
375 " +as/objectives +nb/faults +nb/goals" \
376 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
377 " +dom/ticks +dom/takes" \
378 " -lms,rc,cts,inv,nb/score"
380 void Cmd_Scoreboard_SetFields(int argc)
385 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
389 return; // do nothing, we don't know gametype and scores yet
391 // sbt_fields uses strunzone on the titles!
392 if(!sbt_field_title[0])
393 for(i = 0; i < MAX_SBT_FIELDS; ++i)
394 sbt_field_title[i] = strzone("(null)");
396 // TODO: re enable with gametype dependant cvars?
397 if(argc < 3) // no arguments provided
398 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
401 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
405 if(argv(2) == "default")
406 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
407 else if(argv(2) == "all")
409 string s = "ping pl elo name |"; // scores without a label
410 FOREACH(Scores, true, {
412 if(it != ps_secondary)
413 if(scores_label(it) != "")
414 s = strcat(s, " ", scores_label(it));
416 if(ps_secondary != ps_primary)
417 s = strcat(s, " ", scores_label(ps_secondary));
418 s = strcat(s, " ", scores_label(ps_primary));
419 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
426 hud_fontsize = HUD_GetFontsize("hud_fontsize");
428 for(i = 1; i < argc - 1; ++i)
431 bool nocomplain = false;
432 if(substring(str, 0, 1) == "?")
435 str = substring(str, 1, strlen(str) - 1);
438 slash = strstrofs(str, "/", 0);
441 pattern = substring(str, 0, slash);
442 str = substring(str, slash + 1, strlen(str) - (slash + 1));
444 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
448 strunzone(sbt_field_title[sbt_num_fields]);
449 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
450 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
451 str = strtolower(str);
456 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
457 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
458 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
459 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
460 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
461 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
462 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
463 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
464 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
465 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
468 FOREACH(Scores, true, {
469 if (str == strtolower(scores_label(it))) {
471 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
481 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
485 sbt_field[sbt_num_fields] = j;
488 if(j == ps_secondary)
489 have_secondary = true;
494 if(sbt_num_fields >= MAX_SBT_FIELDS)
498 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
500 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
501 have_secondary = true;
502 if(ps_primary == ps_secondary)
503 have_secondary = true;
504 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
506 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
510 strunzone(sbt_field_title[sbt_num_fields]);
511 for(i = sbt_num_fields; i > 0; --i)
513 sbt_field_title[i] = sbt_field_title[i-1];
514 sbt_field_size[i] = sbt_field_size[i-1];
515 sbt_field[i] = sbt_field[i-1];
517 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
518 sbt_field[0] = SP_NAME;
520 LOG_INFO("fixed missing field 'name'");
524 strunzone(sbt_field_title[sbt_num_fields]);
525 for(i = sbt_num_fields; i > 1; --i)
527 sbt_field_title[i] = sbt_field_title[i-1];
528 sbt_field_size[i] = sbt_field_size[i-1];
529 sbt_field[i] = sbt_field[i-1];
531 sbt_field_title[1] = strzone("|");
532 sbt_field[1] = SP_SEPARATOR;
533 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
535 LOG_INFO("fixed missing field '|'");
538 else if(!have_separator)
540 strunzone(sbt_field_title[sbt_num_fields]);
541 sbt_field_title[sbt_num_fields] = strzone("|");
542 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
543 sbt_field[sbt_num_fields] = SP_SEPARATOR;
545 LOG_INFO("fixed missing field '|'");
549 strunzone(sbt_field_title[sbt_num_fields]);
550 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
551 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
552 sbt_field[sbt_num_fields] = ps_secondary;
554 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
558 strunzone(sbt_field_title[sbt_num_fields]);
559 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
560 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
561 sbt_field[sbt_num_fields] = ps_primary;
563 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
567 sbt_field[sbt_num_fields] = SP_END;
571 vector sbt_field_rgb;
572 string sbt_field_icon0;
573 string sbt_field_icon1;
574 string sbt_field_icon2;
575 vector sbt_field_icon0_rgb;
576 vector sbt_field_icon1_rgb;
577 vector sbt_field_icon2_rgb;
578 string Scoreboard_GetName(entity pl)
580 if(ready_waiting && pl.ready)
582 sbt_field_icon0 = "gfx/scoreboard/player_ready";
586 int f = entcs_GetClientColors(pl.sv_entnum);
588 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
589 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
590 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
591 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
592 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
595 return entcs_GetName(pl.sv_entnum);
597 string Scoreboard_GetField(entity pl, PlayerScoreField field)
599 float tmp, num, denom;
602 sbt_field_rgb = '1 1 1';
603 sbt_field_icon0 = "";
604 sbt_field_icon1 = "";
605 sbt_field_icon2 = "";
606 sbt_field_icon0_rgb = '1 1 1';
607 sbt_field_icon1_rgb = '1 1 1';
608 sbt_field_icon2_rgb = '1 1 1';
613 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
614 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
618 tmp = max(0, min(220, f-80)) / 220;
619 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
625 f = pl.ping_packetloss;
626 tmp = pl.ping_movementloss;
627 if(f == 0 && tmp == 0)
629 str = ftos(ceil(f * 100));
631 str = strcat(str, "~", ftos(ceil(tmp * 100)));
632 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
633 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
637 return Scoreboard_GetName(pl);
640 f = pl.(scores(SP_KILLS));
641 f -= pl.(scores(SP_SUICIDES));
645 num = pl.(scores(SP_KILLS));
646 denom = pl.(scores(SP_DEATHS));
649 sbt_field_rgb = '0 1 0';
650 str = sprintf("%d", num);
651 } else if(num <= 0) {
652 sbt_field_rgb = '1 0 0';
653 str = sprintf("%.1f", num/denom);
655 str = sprintf("%.1f", num/denom);
659 f = pl.(scores(SP_KILLS));
660 f -= pl.(scores(SP_DEATHS));
663 sbt_field_rgb = '0 1 0';
665 sbt_field_rgb = '1 1 1';
667 sbt_field_rgb = '1 0 0';
673 float elo = pl.(scores(SP_ELO));
675 case -1: return "...";
676 case -2: return _("N/A");
677 default: return ftos(elo);
683 float fps = pl.(scores(SP_FPS));
686 sbt_field_rgb = '1 1 1';
687 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
689 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
690 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
694 case SP_DMG: case SP_DMGTAKEN:
695 return sprintf("%.1f k", pl.(scores(field)) / 1000);
697 default: case SP_SCORE:
698 tmp = pl.(scores(field));
699 f = scores_flags(field);
700 if(field == ps_primary)
701 sbt_field_rgb = '1 1 0';
702 else if(field == ps_secondary)
703 sbt_field_rgb = '0 1 1';
705 sbt_field_rgb = '1 1 1';
706 return ScoreString(f, tmp);
711 float sbt_fixcolumnwidth_len;
712 float sbt_fixcolumnwidth_iconlen;
713 float sbt_fixcolumnwidth_marginlen;
715 string Scoreboard_FixColumnWidth(int i, string str)
721 sbt_fixcolumnwidth_iconlen = 0;
723 if(sbt_field_icon0 != "")
725 sz = draw_getimagesize(sbt_field_icon0);
727 if(sbt_fixcolumnwidth_iconlen < f)
728 sbt_fixcolumnwidth_iconlen = f;
731 if(sbt_field_icon1 != "")
733 sz = draw_getimagesize(sbt_field_icon1);
735 if(sbt_fixcolumnwidth_iconlen < f)
736 sbt_fixcolumnwidth_iconlen = f;
739 if(sbt_field_icon2 != "")
741 sz = draw_getimagesize(sbt_field_icon2);
743 if(sbt_fixcolumnwidth_iconlen < f)
744 sbt_fixcolumnwidth_iconlen = f;
747 if(sbt_fixcolumnwidth_iconlen != 0)
749 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
750 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
753 sbt_fixcolumnwidth_marginlen = 0;
755 if(sbt_field[i] == SP_NAME) // name gets all remaining space
758 float remaining_space = 0;
759 for(j = 0; j < sbt_num_fields; ++j)
761 if (sbt_field[i] != SP_SEPARATOR)
762 remaining_space += sbt_field_size[j] + hud_fontsize.x;
763 sbt_field_size[i] = panel_size.x - remaining_space;
765 if (sbt_fixcolumnwidth_iconlen != 0)
766 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
767 float namesize = panel_size.x - remaining_space;
768 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
769 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
771 max_namesize = vid_conwidth - remaining_space;
774 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
776 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
777 if(sbt_field_size[i] < f)
778 sbt_field_size[i] = f;
783 void Scoreboard_initFieldSizes()
785 for(int i = 0; i < sbt_num_fields; ++i)
787 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
788 Scoreboard_FixColumnWidth(i, "");
792 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
795 vector column_dim = eY * panel_size.y;
797 column_dim.y -= 1.25 * hud_fontsize.y;
798 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
799 pos.x += hud_fontsize.x * 0.5;
800 for(i = 0; i < sbt_num_fields; ++i)
802 if(sbt_field[i] == SP_SEPARATOR)
804 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
807 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
808 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
809 pos.x += column_dim.x;
811 if(sbt_field[i] == SP_SEPARATOR)
813 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
814 for(i = sbt_num_fields - 1; i > 0; --i)
816 if(sbt_field[i] == SP_SEPARATOR)
819 pos.x -= sbt_field_size[i];
824 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
825 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
828 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
829 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
830 pos.x -= hud_fontsize.x;
835 pos.y += 1.25 * hud_fontsize.y;
839 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
841 TC(bool, is_self); TC(int, pl_number);
843 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
845 vector h_pos = item_pos;
846 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
847 // alternated rows highlighting
849 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
850 else if((sbt_highlight) && (!(pl_number % 2)))
851 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
853 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
855 vector pos = item_pos;
856 pos.x += hud_fontsize.x * 0.5;
857 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
858 vector tmp = '0 0 0';
860 PlayerScoreField field;
861 for(i = 0; i < sbt_num_fields; ++i)
863 field = sbt_field[i];
864 if(field == SP_SEPARATOR)
867 if(is_spec && field != SP_NAME && field != SP_PING) {
868 pos.x += sbt_field_size[i] + hud_fontsize.x;
871 str = Scoreboard_GetField(pl, field);
872 str = Scoreboard_FixColumnWidth(i, str);
874 pos.x += sbt_field_size[i] + hud_fontsize.x;
876 if(field == SP_NAME) {
877 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
878 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
880 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
881 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
884 tmp.x = sbt_field_size[i] + hud_fontsize.x;
885 if(sbt_field_icon0 != "")
886 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
887 if(sbt_field_icon1 != "")
888 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
889 if(sbt_field_icon2 != "")
890 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
893 if(sbt_field[i] == SP_SEPARATOR)
895 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
896 for(i = sbt_num_fields-1; i > 0; --i)
898 field = sbt_field[i];
899 if(field == SP_SEPARATOR)
902 if(is_spec && field != SP_NAME && field != SP_PING) {
903 pos.x -= sbt_field_size[i] + hud_fontsize.x;
907 str = Scoreboard_GetField(pl, field);
908 str = Scoreboard_FixColumnWidth(i, str);
910 if(field == SP_NAME) {
911 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
912 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
914 tmp.x = sbt_fixcolumnwidth_len;
915 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 tmp.x = sbt_field_size[i];
919 if(sbt_field_icon0 != "")
920 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
921 if(sbt_field_icon1 != "")
922 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
923 if(sbt_field_icon2 != "")
924 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
925 pos.x -= sbt_field_size[i] + hud_fontsize.x;
930 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
933 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
936 vector h_pos = item_pos;
937 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
939 bool complete = (this_team == NUM_SPECTATOR);
942 if((sbt_highlight) && (!(pl_number % 2)))
943 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
945 vector pos = item_pos;
946 pos.x += hud_fontsize.x * 0.5;
947 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
949 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
951 width_limit -= stringwidth("...", false, hud_fontsize);
952 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
953 static float max_name_width = 0;
956 float min_fieldsize = 0;
957 float fieldpadding = hud_fontsize.x * 0.25;
958 if(this_team == NUM_SPECTATOR)
960 if(autocvar_hud_panel_scoreboard_spectators_showping)
961 min_fieldsize = stringwidth("999", false, hud_fontsize);
963 else if(autocvar_hud_panel_scoreboard_others_showscore)
964 min_fieldsize = stringwidth("99", false, hud_fontsize);
965 for(i = 0; pl; pl = pl.sort_next)
967 if(pl.team != this_team)
973 if(this_team == NUM_SPECTATOR)
975 if(autocvar_hud_panel_scoreboard_spectators_showping)
976 field = Scoreboard_GetField(pl, SP_PING);
978 else if(autocvar_hud_panel_scoreboard_others_showscore)
979 field = Scoreboard_GetField(pl, SP_SCORE);
981 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
982 float column_width = stringwidth(str, true, hud_fontsize);
983 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
985 if(column_width > max_name_width)
986 max_name_width = column_width;
987 column_width = max_name_width;
991 fieldsize = stringwidth(field, false, hud_fontsize);
992 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
995 if(pos.x + column_width > width_limit)
1000 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1005 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1006 pos.y += hud_fontsize.y * 1.25;
1010 vector name_pos = pos;
1011 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1012 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1013 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1016 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1017 h_size.y = hud_fontsize.y;
1018 vector field_pos = pos;
1019 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1020 field_pos.x += column_width - h_size.x;
1022 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1023 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1024 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1026 pos.x += column_width;
1027 pos.x += hud_fontsize.x;
1029 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1032 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1034 int max_players = 999;
1035 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1037 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1040 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1041 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1042 height /= team_count;
1045 height -= panel_bg_padding * 2; // - padding
1046 max_players = floor(height / (hud_fontsize.y * 1.25));
1047 if(max_players <= 1)
1049 if(max_players == tm.team_size)
1054 entity me = playerslots[current_player];
1056 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1057 panel_size.y += panel_bg_padding * 2;
1060 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1061 if(panel.current_panel_bg != "0")
1062 end_pos.y += panel_bg_border * 2;
1064 if(panel_bg_padding)
1066 panel_pos += '1 1 0' * panel_bg_padding;
1067 panel_size -= '2 2 0' * panel_bg_padding;
1071 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1075 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1077 pos.y += 1.25 * hud_fontsize.y;
1080 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1082 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1085 // print header row and highlight columns
1086 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1088 // fill the table and draw the rows
1089 bool is_self = false;
1090 bool self_shown = false;
1092 for(pl = players.sort_next; pl; pl = pl.sort_next)
1094 if(pl.team != tm.team)
1096 if(i == max_players - 2 && pl != me)
1098 if(!self_shown && me.team == tm.team)
1100 Scoreboard_DrawItem(pos, rgb, me, true, i);
1102 pos.y += 1.25 * hud_fontsize.y;
1106 if(i >= max_players - 1)
1108 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1111 is_self = (pl.sv_entnum == current_player);
1112 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1115 pos.y += 1.25 * hud_fontsize.y;
1119 panel_size.x += panel_bg_padding * 2; // restore initial width
1123 bool Scoreboard_WouldDraw()
1125 if (MUTATOR_CALLHOOK(DrawScoreboard))
1127 else if (QuickMenu_IsOpened())
1129 else if (HUD_Radar_Clickable())
1131 else if (scoreboard_showscores)
1133 else if (intermission == 1)
1135 else if (intermission == 2)
1137 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1139 else if (scoreboard_showscores_force)
1144 float average_accuracy;
1145 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1147 WepSet weapons_stat = WepSet_GetFromStat();
1148 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1149 int disownedcnt = 0;
1151 FOREACH(Weapons, it != WEP_Null, {
1152 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1154 WepSet set = it.m_wepset;
1155 if(it.spawnflags & WEP_TYPE_OTHER)
1160 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1162 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1169 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1170 if (weapon_cnt <= 0) return pos;
1173 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1175 int columnns = ceil(weapon_cnt / rows);
1177 float weapon_height = 29;
1178 float height = hud_fontsize.y + weapon_height;
1180 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1181 pos.y += 1.25 * hud_fontsize.y;
1182 if(panel.current_panel_bg != "0")
1183 pos.y += panel_bg_border;
1186 panel_size.y = height * rows;
1187 panel_size.y += panel_bg_padding * 2;
1190 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1191 if(panel.current_panel_bg != "0")
1192 end_pos.y += panel_bg_border * 2;
1194 if(panel_bg_padding)
1196 panel_pos += '1 1 0' * panel_bg_padding;
1197 panel_size -= '2 2 0' * panel_bg_padding;
1201 vector tmp = panel_size;
1203 float weapon_width = tmp.x / columnns / rows;
1206 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1210 // column highlighting
1211 for (int i = 0; i < columnns; ++i)
1213 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1216 for (int i = 0; i < rows; ++i)
1217 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1220 average_accuracy = 0;
1221 int weapons_with_stats = 0;
1223 pos.x += weapon_width / 2;
1225 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1228 Accuracy_LoadColors();
1230 float oldposx = pos.x;
1234 FOREACH(Weapons, it != WEP_Null, {
1235 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1237 WepSet set = it.m_wepset;
1238 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1240 if (it.spawnflags & WEP_TYPE_OTHER)
1244 if (weapon_stats >= 0)
1245 weapon_alpha = sbt_fg_alpha;
1247 weapon_alpha = 0.2 * sbt_fg_alpha;
1250 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1252 if (weapon_stats >= 0) {
1253 weapons_with_stats += 1;
1254 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1257 s = sprintf("%d%%", weapon_stats * 100);
1260 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1262 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1263 rgb = Accuracy_GetColor(weapon_stats);
1265 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1267 tmpos.x += weapon_width * rows;
1268 pos.x += weapon_width * rows;
1269 if (rows == 2 && column == columnns - 1) {
1277 if (weapons_with_stats)
1278 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1280 panel_size.x += panel_bg_padding * 2; // restore initial width
1284 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1286 pos.x += hud_fontsize.x * 0.25;
1287 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1288 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1289 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1291 pos.y += hud_fontsize.y;
1296 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1297 float stat_secrets_found, stat_secrets_total;
1298 float stat_monsters_killed, stat_monsters_total;
1302 // get monster stats
1303 stat_monsters_killed = STAT(MONSTERS_KILLED);
1304 stat_monsters_total = STAT(MONSTERS_TOTAL);
1306 // get secrets stats
1307 stat_secrets_found = STAT(SECRETS_FOUND);
1308 stat_secrets_total = STAT(SECRETS_TOTAL);
1310 // get number of rows
1311 if(stat_secrets_total)
1313 if(stat_monsters_total)
1316 // if no rows, return
1320 // draw table header
1321 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1322 pos.y += 1.25 * hud_fontsize.y;
1323 if(panel.current_panel_bg != "0")
1324 pos.y += panel_bg_border;
1327 panel_size.y = hud_fontsize.y * rows;
1328 panel_size.y += panel_bg_padding * 2;
1331 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1332 if(panel.current_panel_bg != "0")
1333 end_pos.y += panel_bg_border * 2;
1335 if(panel_bg_padding)
1337 panel_pos += '1 1 0' * panel_bg_padding;
1338 panel_size -= '2 2 0' * panel_bg_padding;
1342 vector tmp = panel_size;
1345 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1348 if(stat_monsters_total)
1350 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1351 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1355 if(stat_secrets_total)
1357 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1358 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1361 panel_size.x += panel_bg_padding * 2; // restore initial width
1366 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1369 RANKINGS_RECEIVED_CNT = 0;
1370 for (i=RANKINGS_CNT-1; i>=0; --i)
1372 ++RANKINGS_RECEIVED_CNT;
1374 if (RANKINGS_RECEIVED_CNT == 0)
1377 vector hl_rgb = rgb + '0.5 0.5 0.5';
1379 pos.y += hud_fontsize.y;
1380 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1381 pos.y += 1.25 * hud_fontsize.y;
1382 if(panel.current_panel_bg != "0")
1383 pos.y += panel_bg_border;
1388 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1390 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1395 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1397 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1401 float ranksize = 3 * hud_fontsize.x;
1402 float timesize = 5 * hud_fontsize.x;
1403 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1404 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1405 columns = min(columns, RANKINGS_RECEIVED_CNT);
1407 // expand name column to fill the entire row
1408 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1409 namesize += available_space;
1410 columnsize.x += available_space;
1412 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1413 panel_size.y += panel_bg_padding * 2;
1417 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1418 if(panel.current_panel_bg != "0")
1419 end_pos.y += panel_bg_border * 2;
1421 if(panel_bg_padding)
1423 panel_pos += '1 1 0' * panel_bg_padding;
1424 panel_size -= '2 2 0' * panel_bg_padding;
1430 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1432 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1434 int column = 0, j = 0;
1435 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1442 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1443 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1444 else if(!((j + column) & 1) && sbt_highlight)
1445 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1447 str = count_ordinal(i+1);
1448 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1449 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1450 str = ColorTranslateRGB(grecordholder[i]);
1452 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1453 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1455 pos.y += 1.25 * hud_fontsize.y;
1457 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1461 pos.x += panel_size.x / columns;
1462 pos.y = panel_pos.y;
1466 panel_size.x += panel_bg_padding * 2; // restore initial width
1470 void Scoreboard_Draw()
1472 if(!autocvar__hud_configure)
1474 if(!hud_draw_maximized) return;
1476 // frametime checks allow to toggle the scoreboard even when the game is paused
1477 if(scoreboard_active) {
1478 if(hud_configure_menu_open == 1)
1479 scoreboard_fade_alpha = 1;
1480 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1481 if (scoreboard_fadeinspeed && frametime)
1482 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1484 scoreboard_fade_alpha = 1;
1485 if(hud_fontsize_str != autocvar_hud_fontsize)
1487 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1488 Scoreboard_initFieldSizes();
1489 if(hud_fontsize_str)
1490 strunzone(hud_fontsize_str);
1491 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1495 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1496 if (scoreboard_fadeoutspeed && frametime)
1497 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1499 scoreboard_fade_alpha = 0;
1502 if (!scoreboard_fade_alpha)
1506 scoreboard_fade_alpha = 0;
1508 if (autocvar_hud_panel_scoreboard_dynamichud)
1511 HUD_Scale_Disable();
1513 if(scoreboard_fade_alpha <= 0)
1515 panel_fade_alpha *= scoreboard_fade_alpha;
1516 HUD_Panel_LoadCvars();
1518 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1519 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1520 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1521 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1522 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1523 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1525 // don't overlap with con_notify
1526 if(!autocvar__hud_configure)
1527 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1529 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1530 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1531 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1532 panel_size.x = fixed_scoreboard_width;
1534 Scoreboard_UpdatePlayerTeams();
1536 vector pos = panel_pos;
1542 vector sb_heading_fontsize;
1543 sb_heading_fontsize = hud_fontsize * 2;
1544 draw_beginBoldFont();
1545 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1548 pos.y += sb_heading_fontsize.y;
1549 if(panel.current_panel_bg != "0")
1550 pos.y += panel_bg_border;
1552 // Draw the scoreboard
1553 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1556 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1560 vector panel_bg_color_save = panel_bg_color;
1561 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1562 if(panel.current_panel_bg != "0")
1563 team_score_baseoffset.x -= panel_bg_border;
1564 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1566 if(tm.team == NUM_SPECTATOR)
1571 draw_beginBoldFont();
1572 vector rgb = Team_ColorRGB(tm.team);
1573 str = ftos(tm.(teamscores(ts_primary)));
1574 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1575 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1577 if(ts_primary != ts_secondary)
1579 str = ftos(tm.(teamscores(ts_secondary)));
1580 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1581 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1584 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1585 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1586 else if(panel_bg_color_team > 0)
1587 panel_bg_color = rgb * panel_bg_color_team;
1589 panel_bg_color = rgb;
1590 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1592 panel_bg_color = panel_bg_color_save;
1596 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1597 if(tm.team != NUM_SPECTATOR)
1599 // display it anyway
1600 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1603 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1605 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1606 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1608 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1609 if(race_speedaward) {
1610 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, ColorTranslateRGB(race_speedaward_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1611 pos.y += 1.25 * hud_fontsize.y;
1613 if(race_speedaward_alltimebest) {
1614 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, ColorTranslateRGB(race_speedaward_alltimebest_holder)), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1615 pos.y += 1.25 * hud_fontsize.y;
1617 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1620 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1623 for(pl = players.sort_next; pl; pl = pl.sort_next)
1625 if(pl.team == NUM_SPECTATOR)
1627 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1628 if(tm.team == NUM_SPECTATOR)
1630 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1631 draw_beginBoldFont();
1632 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1634 pos.y += 1.25 * hud_fontsize.y;
1636 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1637 pos.y += 1.25 * hud_fontsize.y;
1643 // Print info string
1645 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1646 tl = STAT(TIMELIMIT);
1647 fl = STAT(FRAGLIMIT);
1648 ll = STAT(LEADLIMIT);
1649 if(gametype == MAPINFO_TYPE_LMS)
1652 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1657 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1661 str = strcat(str, _(" or"));
1664 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1665 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1666 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1667 TranslateScoresLabel(teamscores_label(ts_primary))));
1671 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1672 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1673 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1674 TranslateScoresLabel(scores_label(ps_primary))));
1679 if(tl > 0 || fl > 0)
1680 str = strcat(str, _(" or"));
1683 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1684 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1685 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1686 TranslateScoresLabel(teamscores_label(ts_primary))));
1690 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1691 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1692 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1693 TranslateScoresLabel(scores_label(ps_primary))));
1698 pos.y += 1.2 * hud_fontsize.y;
1699 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1701 // print information about respawn status
1702 float respawn_time = STAT(RESPAWN_TIME);
1706 if(respawn_time < 0)
1708 // a negative number means we are awaiting respawn, time value is still the same
1709 respawn_time *= -1; // remove mark now that we checked it
1711 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1712 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1714 str = sprintf(_("^1Respawning in ^3%s^1..."),
1715 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1716 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1718 count_seconds(ceil(respawn_time - time))
1722 else if(time < respawn_time)
1724 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1725 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1726 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1728 count_seconds(ceil(respawn_time - time))
1732 else if(time >= respawn_time)
1733 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1735 pos.y += 1.2 * hud_fontsize.y;
1736 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1739 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;