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(_("Usage:"));
301 LOG_INFO("^2scoreboard_columns_set default");
302 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ..."));
303 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
304 LOG_INFO(_("The following field names are recognized (case insensitive):"));
307 LOG_INFO(strcat("^3name^7 ", _("Name of a player")));
308 LOG_INFO(strcat("^3nick^7 ", _("Name of a player")));
309 LOG_INFO(strcat("^3ping^7 ", _("Ping time")));
310 LOG_INFO(strcat("^3pl^7 ", _("Packet loss")));
311 LOG_INFO(strcat("^3elo^7 ", _("Player ELO")));
312 LOG_INFO(strcat("^3fps^7 ", _("Player FPS")));
313 LOG_INFO(strcat("^3kills^7 ", _("Number of kills")));
314 LOG_INFO(strcat("^3deaths^7 ", _("Number of deaths")));
315 LOG_INFO(strcat("^3suicides^7 ", _("Number of suicides")));
316 LOG_INFO(strcat("^3frags^7 ", _("kills - suicides")));
317 LOG_INFO(strcat("^3teamkills^7 ", _("Number of teamkills")));
318 LOG_INFO(strcat("^3kd^7 ", _("The kill-death ratio")));
319 LOG_INFO(strcat("^3dmg^7 ", _("The total damage done")));
320 LOG_INFO(strcat("^3dmgtaken^7 ", _("The total damage taken")));
321 LOG_INFO(strcat("^3sum^7 ", _("kills - deaths")));
322 LOG_INFO(strcat("^3caps^7 ", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
323 LOG_INFO(strcat("^3pickups^7 ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
324 LOG_INFO(strcat("^3captime^7 ", _("Time of fastest cap (CTF)")));
325 LOG_INFO(strcat("^3fckills^7 ", _("Number of flag carrier kills")));
326 LOG_INFO(strcat("^3returns^7 ", _("Number of flag returns")));
327 LOG_INFO(strcat("^3drops^7 ", _("Number of flag drops")));
328 LOG_INFO(strcat("^3lives^7 ", _("Number of lives (LMS)")));
329 LOG_INFO(strcat("^3rank^7 ", _("Player rank")));
330 LOG_INFO(strcat("^3pushes^7 ", _("Number of players pushed into void")));
331 LOG_INFO(strcat("^3destroyed^7 ", _("Number of keys destroyed by pushing them into void")));
332 LOG_INFO(strcat("^3kckills^7 ", _("Number of keys carrier kills")));
333 LOG_INFO(strcat("^3losses^7 ", _("Number of times a key was lost")));
334 LOG_INFO(strcat("^3laps^7 ", _("Number of laps finished (race/cts)")));
335 LOG_INFO(strcat("^3time^7 ", _("Total time raced (race/cts)")));
336 LOG_INFO(strcat("^3fastest^7 ", _("Time of fastest lap (race/cts)")));
337 LOG_INFO(strcat("^3ticks^7 ", _("Number of ticks (DOM)")));
338 LOG_INFO(strcat("^3takes^7 ", _("Number of domination points taken (DOM)")));
339 LOG_INFO(strcat("^3bckills^7 ", _("Number of ball carrier kills")));
340 LOG_INFO(strcat("^3bctime^7 ", _("Total amount of time holding the ball in Keepaway")));
341 LOG_INFO(strcat("^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."));
350 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
351 "include/exclude ALL teams/noteams game modes."));
354 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4"));
355 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
356 "right of the vertical bar aligned to the right."));
357 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
358 "other gamemodes except DM."));
361 // NOTE: adding a gametype with ? to not warn for an optional field
362 // make sure it's excluded in a previous exclusive rule, if any
363 // otherwise the previous exclusive rule warns anyway
364 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
365 #define SCOREBOARD_DEFAULT_COLUMNS \
366 "ping pl fps name |" \
367 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
368 " -teams,lms/deaths +ft,tdm/deaths" \
370 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
371 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
372 " +tdm,ft,dom,ons,as/teamkills"\
373 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
374 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
375 " +lms/lives +lms/rank" \
376 " +kh/kckills +kh/losses +kh/caps" \
377 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
378 " +as/objectives +nb/faults +nb/goals" \
379 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
380 " +dom/ticks +dom/takes" \
381 " -lms,rc,cts,inv,nb/score"
383 void Cmd_Scoreboard_SetFields(int argc)
388 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
392 return; // do nothing, we don't know gametype and scores yet
394 // sbt_fields uses strunzone on the titles!
395 if(!sbt_field_title[0])
396 for(i = 0; i < MAX_SBT_FIELDS; ++i)
397 sbt_field_title[i] = strzone("(null)");
399 // TODO: re enable with gametype dependant cvars?
400 if(argc < 3) // no arguments provided
401 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
404 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
408 if(argv(2) == "default")
409 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
410 else if(argv(2) == "all")
412 string s = "ping pl name |"; // scores without a label
413 FOREACH(Scores, true, {
415 if(it != ps_secondary)
416 if(scores_label(it) != "")
417 s = strcat(s, " ", scores_label(it));
419 if(ps_secondary != ps_primary)
420 s = strcat(s, " ", scores_label(ps_secondary));
421 s = strcat(s, " ", scores_label(ps_primary));
422 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
429 hud_fontsize = HUD_GetFontsize("hud_fontsize");
431 for(i = 1; i < argc - 1; ++i)
434 bool nocomplain = false;
435 if(substring(str, 0, 1) == "?")
438 str = substring(str, 1, strlen(str) - 1);
441 slash = strstrofs(str, "/", 0);
444 pattern = substring(str, 0, slash);
445 str = substring(str, slash + 1, strlen(str) - (slash + 1));
447 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
451 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(str));
452 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
453 str = strtolower(str);
458 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
459 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
460 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
461 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
462 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
463 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
464 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
465 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
466 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
467 case "fps": sbt_field[sbt_num_fields] = SP_FPS; break;
470 FOREACH(Scores, true, {
471 if (str == strtolower(scores_label(it))) {
473 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
483 LOG_INFOF("^1Error:^7 Unknown score field: '%s'", str);
487 sbt_field[sbt_num_fields] = j;
490 if(j == ps_secondary)
491 have_secondary = true;
496 if(sbt_num_fields >= MAX_SBT_FIELDS)
500 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
502 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
503 have_secondary = true;
504 if(ps_primary == ps_secondary)
505 have_secondary = true;
506 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
508 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
512 strunzone(sbt_field_title[sbt_num_fields]);
513 for(i = sbt_num_fields; i > 0; --i)
515 sbt_field_title[i] = sbt_field_title[i-1];
516 sbt_field_size[i] = sbt_field_size[i-1];
517 sbt_field[i] = sbt_field[i-1];
519 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
520 sbt_field[0] = SP_NAME;
522 LOG_INFO("fixed missing field 'name'");
526 strunzone(sbt_field_title[sbt_num_fields]);
527 for(i = sbt_num_fields; i > 1; --i)
529 sbt_field_title[i] = sbt_field_title[i-1];
530 sbt_field_size[i] = sbt_field_size[i-1];
531 sbt_field[i] = sbt_field[i-1];
533 sbt_field_title[1] = strzone("|");
534 sbt_field[1] = SP_SEPARATOR;
535 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
537 LOG_INFO("fixed missing field '|'");
540 else if(!have_separator)
542 strcpy(sbt_field_title[sbt_num_fields], "|");
543 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
544 sbt_field[sbt_num_fields] = SP_SEPARATOR;
546 LOG_INFO("fixed missing field '|'");
550 strcpy(sbt_field_title[sbt_num_fields], 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 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
559 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
560 sbt_field[sbt_num_fields] = ps_primary;
562 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
566 sbt_field[sbt_num_fields] = SP_END;
570 vector sbt_field_rgb;
571 string sbt_field_icon0;
572 string sbt_field_icon1;
573 string sbt_field_icon2;
574 vector sbt_field_icon0_rgb;
575 vector sbt_field_icon1_rgb;
576 vector sbt_field_icon2_rgb;
577 string Scoreboard_GetName(entity pl)
579 if(ready_waiting && pl.ready)
581 sbt_field_icon0 = "gfx/scoreboard/player_ready";
585 int f = entcs_GetClientColors(pl.sv_entnum);
587 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
588 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
589 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
590 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
591 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
594 return entcs_GetName(pl.sv_entnum);
596 string Scoreboard_GetField(entity pl, PlayerScoreField field)
598 float tmp, num, denom;
601 sbt_field_rgb = '1 1 1';
602 sbt_field_icon0 = "";
603 sbt_field_icon1 = "";
604 sbt_field_icon2 = "";
605 sbt_field_icon0_rgb = '1 1 1';
606 sbt_field_icon1_rgb = '1 1 1';
607 sbt_field_icon2_rgb = '1 1 1';
612 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
613 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
617 tmp = max(0, min(220, f-80)) / 220;
618 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
624 f = pl.ping_packetloss;
625 tmp = pl.ping_movementloss;
626 if(f == 0 && tmp == 0)
628 str = ftos(ceil(f * 100));
630 str = strcat(str, "~", ftos(ceil(tmp * 100)));
631 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
632 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
636 return Scoreboard_GetName(pl);
639 f = pl.(scores(SP_KILLS));
640 f -= pl.(scores(SP_SUICIDES));
644 num = pl.(scores(SP_KILLS));
645 denom = pl.(scores(SP_DEATHS));
648 sbt_field_rgb = '0 1 0';
649 str = sprintf("%d", num);
650 } else if(num <= 0) {
651 sbt_field_rgb = '1 0 0';
652 str = sprintf("%.1f", num/denom);
654 str = sprintf("%.1f", num/denom);
658 f = pl.(scores(SP_KILLS));
659 f -= pl.(scores(SP_DEATHS));
662 sbt_field_rgb = '0 1 0';
664 sbt_field_rgb = '1 1 1';
666 sbt_field_rgb = '1 0 0';
672 float elo = pl.(scores(SP_ELO));
674 case -1: return "...";
675 case -2: return _("N/A");
676 default: return ftos(elo);
682 float fps = pl.(scores(SP_FPS));
685 sbt_field_rgb = '1 1 1';
686 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
688 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
689 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
693 case SP_DMG: case SP_DMGTAKEN:
694 return sprintf("%.1f k", pl.(scores(field)) / 1000);
696 default: case SP_SCORE:
697 tmp = pl.(scores(field));
698 f = scores_flags(field);
699 if(field == ps_primary)
700 sbt_field_rgb = '1 1 0';
701 else if(field == ps_secondary)
702 sbt_field_rgb = '0 1 1';
704 sbt_field_rgb = '1 1 1';
705 return ScoreString(f, tmp);
710 float sbt_fixcolumnwidth_len;
711 float sbt_fixcolumnwidth_iconlen;
712 float sbt_fixcolumnwidth_marginlen;
714 string Scoreboard_FixColumnWidth(int i, string str)
720 sbt_fixcolumnwidth_iconlen = 0;
722 if(sbt_field_icon0 != "")
724 sz = draw_getimagesize(sbt_field_icon0);
726 if(sbt_fixcolumnwidth_iconlen < f)
727 sbt_fixcolumnwidth_iconlen = f;
730 if(sbt_field_icon1 != "")
732 sz = draw_getimagesize(sbt_field_icon1);
734 if(sbt_fixcolumnwidth_iconlen < f)
735 sbt_fixcolumnwidth_iconlen = f;
738 if(sbt_field_icon2 != "")
740 sz = draw_getimagesize(sbt_field_icon2);
742 if(sbt_fixcolumnwidth_iconlen < f)
743 sbt_fixcolumnwidth_iconlen = f;
746 if(sbt_fixcolumnwidth_iconlen != 0)
748 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
749 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
752 sbt_fixcolumnwidth_marginlen = 0;
754 if(sbt_field[i] == SP_NAME) // name gets all remaining space
757 float remaining_space = 0;
758 for(j = 0; j < sbt_num_fields; ++j)
760 if (sbt_field[i] != SP_SEPARATOR)
761 remaining_space += sbt_field_size[j] + hud_fontsize.x;
762 sbt_field_size[i] = panel_size.x - remaining_space;
764 if (sbt_fixcolumnwidth_iconlen != 0)
765 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
766 float namesize = panel_size.x - remaining_space;
767 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
768 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
770 max_namesize = vid_conwidth - remaining_space;
773 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
775 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
776 if(sbt_field_size[i] < f)
777 sbt_field_size[i] = f;
782 void Scoreboard_initFieldSizes()
784 for(int i = 0; i < sbt_num_fields; ++i)
786 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
787 Scoreboard_FixColumnWidth(i, "");
791 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
794 vector column_dim = eY * panel_size.y;
796 column_dim.y -= 1.25 * hud_fontsize.y;
797 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
798 pos.x += hud_fontsize.x * 0.5;
799 for(i = 0; i < sbt_num_fields; ++i)
801 if(sbt_field[i] == SP_SEPARATOR)
803 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
806 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
807 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
808 pos.x += column_dim.x;
810 if(sbt_field[i] == SP_SEPARATOR)
812 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
813 for(i = sbt_num_fields - 1; i > 0; --i)
815 if(sbt_field[i] == SP_SEPARATOR)
818 pos.x -= sbt_field_size[i];
823 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
824 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
827 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
828 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
829 pos.x -= hud_fontsize.x;
834 pos.y += 1.25 * hud_fontsize.y;
838 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
840 TC(bool, is_self); TC(int, pl_number);
842 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
844 vector h_pos = item_pos;
845 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
846 // alternated rows highlighting
848 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
849 else if((sbt_highlight) && (!(pl_number % 2)))
850 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
852 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
854 vector pos = item_pos;
855 pos.x += hud_fontsize.x * 0.5;
856 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
857 vector tmp = '0 0 0';
859 PlayerScoreField field;
860 for(i = 0; i < sbt_num_fields; ++i)
862 field = sbt_field[i];
863 if(field == SP_SEPARATOR)
866 if(is_spec && field != SP_NAME && field != SP_PING) {
867 pos.x += sbt_field_size[i] + hud_fontsize.x;
870 str = Scoreboard_GetField(pl, field);
871 str = Scoreboard_FixColumnWidth(i, str);
873 pos.x += sbt_field_size[i] + hud_fontsize.x;
875 if(field == SP_NAME) {
876 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
877 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
879 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
880 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
883 tmp.x = sbt_field_size[i] + hud_fontsize.x;
884 if(sbt_field_icon0 != "")
885 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
886 if(sbt_field_icon1 != "")
887 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
888 if(sbt_field_icon2 != "")
889 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
892 if(sbt_field[i] == SP_SEPARATOR)
894 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
895 for(i = sbt_num_fields-1; i > 0; --i)
897 field = sbt_field[i];
898 if(field == SP_SEPARATOR)
901 if(is_spec && field != SP_NAME && field != SP_PING) {
902 pos.x -= sbt_field_size[i] + hud_fontsize.x;
906 str = Scoreboard_GetField(pl, field);
907 str = Scoreboard_FixColumnWidth(i, str);
909 if(field == SP_NAME) {
910 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
911 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
913 tmp.x = sbt_fixcolumnwidth_len;
914 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
917 tmp.x = sbt_field_size[i];
918 if(sbt_field_icon0 != "")
919 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
920 if(sbt_field_icon1 != "")
921 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
922 if(sbt_field_icon2 != "")
923 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
924 pos.x -= sbt_field_size[i] + hud_fontsize.x;
929 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
932 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
935 vector h_pos = item_pos;
936 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
938 bool complete = (this_team == NUM_SPECTATOR);
941 if((sbt_highlight) && (!(pl_number % 2)))
942 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
944 vector pos = item_pos;
945 pos.x += hud_fontsize.x * 0.5;
946 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
948 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
950 width_limit -= stringwidth("...", false, hud_fontsize);
951 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
952 static float max_name_width = 0;
955 float min_fieldsize = 0;
956 float fieldpadding = hud_fontsize.x * 0.25;
957 if(this_team == NUM_SPECTATOR)
959 if(autocvar_hud_panel_scoreboard_spectators_showping)
960 min_fieldsize = stringwidth("999", false, hud_fontsize);
962 else if(autocvar_hud_panel_scoreboard_others_showscore)
963 min_fieldsize = stringwidth("99", false, hud_fontsize);
964 for(i = 0; pl; pl = pl.sort_next)
966 if(pl.team != this_team)
972 if(this_team == NUM_SPECTATOR)
974 if(autocvar_hud_panel_scoreboard_spectators_showping)
975 field = Scoreboard_GetField(pl, SP_PING);
977 else if(autocvar_hud_panel_scoreboard_others_showscore)
978 field = Scoreboard_GetField(pl, SP_SCORE);
980 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
981 float column_width = stringwidth(str, true, hud_fontsize);
982 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
984 if(column_width > max_name_width)
985 max_name_width = column_width;
986 column_width = max_name_width;
990 fieldsize = stringwidth(field, false, hud_fontsize);
991 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
994 if(pos.x + column_width > width_limit)
999 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1004 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1005 pos.y += hud_fontsize.y * 1.25;
1009 vector name_pos = pos;
1010 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1011 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1012 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1015 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1016 h_size.y = hud_fontsize.y;
1017 vector field_pos = pos;
1018 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1019 field_pos.x += column_width - h_size.x;
1021 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1022 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1023 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1025 pos.x += column_width;
1026 pos.x += hud_fontsize.x;
1028 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1031 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1033 int max_players = 999;
1034 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1036 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1039 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1040 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1041 height /= team_count;
1044 height -= panel_bg_padding * 2; // - padding
1045 max_players = floor(height / (hud_fontsize.y * 1.25));
1046 if(max_players <= 1)
1048 if(max_players == tm.team_size)
1053 entity me = playerslots[current_player];
1055 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1056 panel_size.y += panel_bg_padding * 2;
1059 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1060 if(panel.current_panel_bg != "0")
1061 end_pos.y += panel_bg_border * 2;
1063 if(panel_bg_padding)
1065 panel_pos += '1 1 0' * panel_bg_padding;
1066 panel_size -= '2 2 0' * panel_bg_padding;
1070 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1074 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1076 pos.y += 1.25 * hud_fontsize.y;
1079 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1081 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1084 // print header row and highlight columns
1085 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1087 // fill the table and draw the rows
1088 bool is_self = false;
1089 bool self_shown = false;
1091 for(pl = players.sort_next; pl; pl = pl.sort_next)
1093 if(pl.team != tm.team)
1095 if(i == max_players - 2 && pl != me)
1097 if(!self_shown && me.team == tm.team)
1099 Scoreboard_DrawItem(pos, rgb, me, true, i);
1101 pos.y += 1.25 * hud_fontsize.y;
1105 if(i >= max_players - 1)
1107 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1110 is_self = (pl.sv_entnum == current_player);
1111 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1114 pos.y += 1.25 * hud_fontsize.y;
1118 panel_size.x += panel_bg_padding * 2; // restore initial width
1122 bool Scoreboard_WouldDraw()
1124 if (MUTATOR_CALLHOOK(DrawScoreboard))
1126 else if (QuickMenu_IsOpened())
1128 else if (HUD_Radar_Clickable())
1130 else if (scoreboard_showscores)
1132 else if (intermission == 1)
1134 else if (intermission == 2)
1136 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1138 else if (scoreboard_showscores_force)
1143 float average_accuracy;
1144 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1146 WepSet weapons_stat = WepSet_GetFromStat();
1147 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1148 int disownedcnt = 0;
1150 FOREACH(Weapons, it != WEP_Null, {
1151 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1153 WepSet set = it.m_wepset;
1154 if(it.spawnflags & WEP_TYPE_OTHER)
1159 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1161 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1168 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1169 if (weapon_cnt <= 0) return pos;
1172 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1174 int columnns = ceil(weapon_cnt / rows);
1176 float weapon_height = 29;
1177 float height = hud_fontsize.y + weapon_height;
1179 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1180 pos.y += 1.25 * hud_fontsize.y;
1181 if(panel.current_panel_bg != "0")
1182 pos.y += panel_bg_border;
1185 panel_size.y = height * rows;
1186 panel_size.y += panel_bg_padding * 2;
1189 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1190 if(panel.current_panel_bg != "0")
1191 end_pos.y += panel_bg_border * 2;
1193 if(panel_bg_padding)
1195 panel_pos += '1 1 0' * panel_bg_padding;
1196 panel_size -= '2 2 0' * panel_bg_padding;
1200 vector tmp = panel_size;
1202 float weapon_width = tmp.x / columnns / rows;
1205 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1209 // column highlighting
1210 for (int i = 0; i < columnns; ++i)
1212 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1215 for (int i = 0; i < rows; ++i)
1216 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1219 average_accuracy = 0;
1220 int weapons_with_stats = 0;
1222 pos.x += weapon_width / 2;
1224 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1227 Accuracy_LoadColors();
1229 float oldposx = pos.x;
1233 FOREACH(Weapons, it != WEP_Null, {
1234 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1236 WepSet set = it.m_wepset;
1237 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1239 if (it.spawnflags & WEP_TYPE_OTHER)
1243 if (weapon_stats >= 0)
1244 weapon_alpha = sbt_fg_alpha;
1246 weapon_alpha = 0.2 * sbt_fg_alpha;
1249 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1251 if (weapon_stats >= 0) {
1252 weapons_with_stats += 1;
1253 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1256 s = sprintf("%d%%", weapon_stats * 100);
1259 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1261 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1262 rgb = Accuracy_GetColor(weapon_stats);
1264 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1266 tmpos.x += weapon_width * rows;
1267 pos.x += weapon_width * rows;
1268 if (rows == 2 && column == columnns - 1) {
1276 if (weapons_with_stats)
1277 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1279 panel_size.x += panel_bg_padding * 2; // restore initial width
1283 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1285 pos.x += hud_fontsize.x * 0.25;
1286 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1287 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1288 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1290 pos.y += hud_fontsize.y;
1295 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1296 float stat_secrets_found, stat_secrets_total;
1297 float stat_monsters_killed, stat_monsters_total;
1301 // get monster stats
1302 stat_monsters_killed = STAT(MONSTERS_KILLED);
1303 stat_monsters_total = STAT(MONSTERS_TOTAL);
1305 // get secrets stats
1306 stat_secrets_found = STAT(SECRETS_FOUND);
1307 stat_secrets_total = STAT(SECRETS_TOTAL);
1309 // get number of rows
1310 if(stat_secrets_total)
1312 if(stat_monsters_total)
1315 // if no rows, return
1319 // draw table header
1320 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1321 pos.y += 1.25 * hud_fontsize.y;
1322 if(panel.current_panel_bg != "0")
1323 pos.y += panel_bg_border;
1326 panel_size.y = hud_fontsize.y * rows;
1327 panel_size.y += panel_bg_padding * 2;
1330 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1331 if(panel.current_panel_bg != "0")
1332 end_pos.y += panel_bg_border * 2;
1334 if(panel_bg_padding)
1336 panel_pos += '1 1 0' * panel_bg_padding;
1337 panel_size -= '2 2 0' * panel_bg_padding;
1341 vector tmp = panel_size;
1344 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1347 if(stat_monsters_total)
1349 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1350 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1354 if(stat_secrets_total)
1356 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1357 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1360 panel_size.x += panel_bg_padding * 2; // restore initial width
1365 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1368 RANKINGS_RECEIVED_CNT = 0;
1369 for (i=RANKINGS_CNT-1; i>=0; --i)
1371 ++RANKINGS_RECEIVED_CNT;
1373 if (RANKINGS_RECEIVED_CNT == 0)
1376 vector hl_rgb = rgb + '0.5 0.5 0.5';
1378 pos.y += hud_fontsize.y;
1379 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1380 pos.y += 1.25 * hud_fontsize.y;
1381 if(panel.current_panel_bg != "0")
1382 pos.y += panel_bg_border;
1387 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1389 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1394 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1396 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1400 float ranksize = 3 * hud_fontsize.x;
1401 float timesize = 5 * hud_fontsize.x;
1402 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1403 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1404 columns = min(columns, RANKINGS_RECEIVED_CNT);
1406 // expand name column to fill the entire row
1407 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1408 namesize += available_space;
1409 columnsize.x += available_space;
1411 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1412 panel_size.y += panel_bg_padding * 2;
1416 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1417 if(panel.current_panel_bg != "0")
1418 end_pos.y += panel_bg_border * 2;
1420 if(panel_bg_padding)
1422 panel_pos += '1 1 0' * panel_bg_padding;
1423 panel_size -= '2 2 0' * panel_bg_padding;
1429 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1431 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1433 int column = 0, j = 0;
1434 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1441 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1442 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1443 else if(!((j + column) & 1) && sbt_highlight)
1444 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1446 str = count_ordinal(i+1);
1447 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1448 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1449 str = ColorTranslateRGB(grecordholder[i]);
1451 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1452 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1454 pos.y += 1.25 * hud_fontsize.y;
1456 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1460 pos.x += panel_size.x / columns;
1461 pos.y = panel_pos.y;
1465 panel_size.x += panel_bg_padding * 2; // restore initial width
1469 void Scoreboard_Draw()
1471 if(!autocvar__hud_configure)
1473 if(!hud_draw_maximized) return;
1475 // frametime checks allow to toggle the scoreboard even when the game is paused
1476 if(scoreboard_active) {
1477 if(hud_configure_menu_open == 1)
1478 scoreboard_fade_alpha = 1;
1479 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1480 if (scoreboard_fadeinspeed && frametime)
1481 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1483 scoreboard_fade_alpha = 1;
1484 if(hud_fontsize_str != autocvar_hud_fontsize)
1486 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1487 Scoreboard_initFieldSizes();
1488 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1492 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1493 if (scoreboard_fadeoutspeed && frametime)
1494 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1496 scoreboard_fade_alpha = 0;
1499 if (!scoreboard_fade_alpha)
1503 scoreboard_fade_alpha = 0;
1505 if (autocvar_hud_panel_scoreboard_dynamichud)
1508 HUD_Scale_Disable();
1510 if(scoreboard_fade_alpha <= 0)
1512 panel_fade_alpha *= scoreboard_fade_alpha;
1513 HUD_Panel_LoadCvars();
1515 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1516 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1517 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1518 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1519 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1520 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1522 // don't overlap with con_notify
1523 if(!autocvar__hud_configure)
1524 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1526 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1527 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1528 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1529 panel_size.x = fixed_scoreboard_width;
1531 Scoreboard_UpdatePlayerTeams();
1533 vector pos = panel_pos;
1539 vector sb_heading_fontsize;
1540 sb_heading_fontsize = hud_fontsize * 2;
1541 draw_beginBoldFont();
1542 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1545 pos.y += sb_heading_fontsize.y;
1546 if(panel.current_panel_bg != "0")
1547 pos.y += panel_bg_border;
1549 // Draw the scoreboard
1550 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1553 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1557 vector panel_bg_color_save = panel_bg_color;
1558 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1559 if(panel.current_panel_bg != "0")
1560 team_score_baseoffset.x -= panel_bg_border;
1561 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1563 if(tm.team == NUM_SPECTATOR)
1568 draw_beginBoldFont();
1569 vector rgb = Team_ColorRGB(tm.team);
1570 str = ftos(tm.(teamscores(ts_primary)));
1571 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1572 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1574 if(ts_primary != ts_secondary)
1576 str = ftos(tm.(teamscores(ts_secondary)));
1577 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1578 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1581 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1582 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1583 else if(panel_bg_color_team > 0)
1584 panel_bg_color = rgb * panel_bg_color_team;
1586 panel_bg_color = rgb;
1587 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1589 panel_bg_color = panel_bg_color_save;
1593 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1594 if(tm.team != NUM_SPECTATOR)
1596 // display it anyway
1597 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1600 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1602 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1603 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1605 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1606 if(race_speedaward) {
1607 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);
1608 pos.y += 1.25 * hud_fontsize.y;
1610 if(race_speedaward_alltimebest) {
1611 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);
1612 pos.y += 1.25 * hud_fontsize.y;
1614 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1617 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1620 for(pl = players.sort_next; pl; pl = pl.sort_next)
1622 if(pl.team == NUM_SPECTATOR)
1624 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1625 if(tm.team == NUM_SPECTATOR)
1627 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1628 draw_beginBoldFont();
1629 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1631 pos.y += 1.25 * hud_fontsize.y;
1633 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1634 pos.y += 1.25 * hud_fontsize.y;
1640 // Print info string
1642 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1643 tl = STAT(TIMELIMIT);
1644 fl = STAT(FRAGLIMIT);
1645 ll = STAT(LEADLIMIT);
1646 if(gametype == MAPINFO_TYPE_LMS)
1649 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1654 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1658 str = strcat(str, _(" or"));
1661 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
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 ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
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 if(tl > 0 || fl > 0)
1677 str = strcat(str, _(" or"));
1680 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1681 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1682 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1683 TranslateScoresLabel(teamscores_label(ts_primary))));
1687 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1688 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1689 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1690 TranslateScoresLabel(scores_label(ps_primary))));
1695 pos.y += 1.2 * hud_fontsize.y;
1696 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1698 // print information about respawn status
1699 float respawn_time = STAT(RESPAWN_TIME);
1703 if(respawn_time < 0)
1705 // a negative number means we are awaiting respawn, time value is still the same
1706 respawn_time *= -1; // remove mark now that we checked it
1708 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1709 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1711 str = sprintf(_("^1Respawning in ^3%s^1..."),
1712 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1713 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1715 count_seconds(ceil(respawn_time - time))
1719 else if(time < respawn_time)
1721 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1722 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1723 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1725 count_seconds(ceil(respawn_time - time))
1729 else if(time >= respawn_time)
1730 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1732 pos.y += 1.2 * hud_fontsize.y;
1733 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1736 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;