1 #include "scoreboard.qh"
3 #include <client/autocvars.qh>
4 #include <client/defs.qh>
5 #include <client/main.qh>
6 #include <client/miscfunctions.qh>
7 #include "quickmenu.qh"
8 #include <common/ent_cs.qh>
9 #include <common/constants.qh>
10 #include <common/net_linked.qh>
11 #include <common/mapinfo.qh>
12 #include <common/minigames/cl_minigames.qh>
13 #include <common/scores.qh>
14 #include <common/stats.qh>
15 #include <common/teams.qh>
19 const int MAX_SBT_FIELDS = MAX_SCORE;
21 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
22 float sbt_field_size[MAX_SBT_FIELDS + 1];
23 string sbt_field_title[MAX_SBT_FIELDS + 1];
26 string autocvar_hud_fontsize;
27 string hud_fontsize_str;
32 float sbt_fg_alpha_self;
34 float sbt_highlight_alpha;
35 float sbt_highlight_alpha_self;
37 // provide basic panel cvars to old clients
38 // TODO remove them after a future release (0.8.2+)
39 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
40 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
41 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
42 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
43 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
44 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
45 noref string autocvar_hud_panel_scoreboard_bg_border = "";
46 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
48 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
49 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
50 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
51 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
52 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
53 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
54 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
55 bool autocvar_hud_panel_scoreboard_table_highlight = true;
56 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
57 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
58 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
59 float autocvar_hud_panel_scoreboard_namesize = 15;
61 bool autocvar_hud_panel_scoreboard_accuracy = true;
62 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
63 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
64 bool autocvar_hud_panel_scoreboard_ctf_leaderboard = true;
66 bool autocvar_hud_panel_scoreboard_dynamichud = false;
68 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
69 bool autocvar_hud_panel_scoreboard_others_showscore = true;
70 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
71 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
72 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
74 // wrapper to put all possible scores titles through gettext
75 string TranslateScoresLabel(string l)
79 case "bckills": return CTX(_("SCO^bckills"));
80 case "bctime": return CTX(_("SCO^bctime"));
81 case "caps": return CTX(_("SCO^caps"));
82 case "captime": return CTX(_("SCO^captime"));
83 case "deaths": return CTX(_("SCO^deaths"));
84 case "destroyed": return CTX(_("SCO^destroyed"));
85 case "dmg": return CTX(_("SCO^damage"));
86 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
87 case "drops": return CTX(_("SCO^drops"));
88 case "faults": return CTX(_("SCO^faults"));
89 case "fckills": return CTX(_("SCO^fckills"));
90 case "goals": return CTX(_("SCO^goals"));
91 case "kckills": return CTX(_("SCO^kckills"));
92 case "kdratio": return CTX(_("SCO^kdratio"));
93 case "kd": return CTX(_("SCO^k/d"));
94 case "kdr": return CTX(_("SCO^kdr"));
95 case "kills": return CTX(_("SCO^kills"));
96 case "teamkills": return CTX(_("SCO^teamkills"));
97 case "laps": return CTX(_("SCO^laps"));
98 case "lives": return CTX(_("SCO^lives"));
99 case "losses": return CTX(_("SCO^losses"));
100 case "name": return CTX(_("SCO^name"));
101 case "sum": return CTX(_("SCO^sum"));
102 case "nick": return CTX(_("SCO^nick"));
103 case "objectives": return CTX(_("SCO^objectives"));
104 case "pickups": return CTX(_("SCO^pickups"));
105 case "ping": return CTX(_("SCO^ping"));
106 case "pl": return CTX(_("SCO^pl"));
107 case "pushes": return CTX(_("SCO^pushes"));
108 case "rank": return CTX(_("SCO^rank"));
109 case "returns": return CTX(_("SCO^returns"));
110 case "revivals": return CTX(_("SCO^revivals"));
111 case "rounds": return CTX(_("SCO^rounds won"));
112 case "score": return CTX(_("SCO^score"));
113 case "suicides": return CTX(_("SCO^suicides"));
114 case "takes": return CTX(_("SCO^takes"));
115 case "ticks": return CTX(_("SCO^ticks"));
120 void Scoreboard_InitScores()
124 ps_primary = ps_secondary = NULL;
125 ts_primary = ts_secondary = -1;
126 FOREACH(Scores, true, {
127 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
128 if(f == SFL_SORT_PRIO_PRIMARY)
130 if(f == SFL_SORT_PRIO_SECONDARY)
133 if(ps_secondary == NULL)
134 ps_secondary = ps_primary;
136 for(i = 0; i < MAX_TEAMSCORE; ++i)
138 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
139 if(f == SFL_SORT_PRIO_PRIMARY)
141 if(f == SFL_SORT_PRIO_SECONDARY)
144 if(ts_secondary == -1)
145 ts_secondary = ts_primary;
147 Cmd_Scoreboard_SetFields(0);
151 void Scoreboard_UpdatePlayerTeams()
155 for(pl = players.sort_next; pl; pl = pl.sort_next)
158 int Team = entcs_GetScoreTeam(pl.sv_entnum);
159 if(SetTeam(pl, Team))
162 Scoreboard_UpdatePlayerPos(pl);
166 pl = players.sort_next;
171 print(strcat("PNUM: ", ftos(num), "\n"));
176 int Scoreboard_CompareScore(int vl, int vr, int f)
178 TC(int, vl); TC(int, vr); TC(int, f);
179 if(f & SFL_ZERO_IS_WORST)
181 if(vl == 0 && vr != 0)
183 if(vl != 0 && vr == 0)
187 return IS_INCREASING(f);
189 return IS_DECREASING(f);
193 float Scoreboard_ComparePlayerScores(entity left, entity right)
196 vl = entcs_GetTeam(left.sv_entnum);
197 vr = entcs_GetTeam(right.sv_entnum);
209 if(vl == NUM_SPECTATOR)
211 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
213 if(!left.gotscores && right.gotscores)
218 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
222 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
226 FOREACH(Scores, true, {
227 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
228 if (r >= 0) return r;
231 if (left.sv_entnum < right.sv_entnum)
237 void Scoreboard_UpdatePlayerPos(entity player)
240 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
242 SORT_SWAP(player, ent);
244 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
246 SORT_SWAP(ent, player);
250 float Scoreboard_CompareTeamScores(entity left, entity right)
254 if(left.team == NUM_SPECTATOR)
256 if(right.team == NUM_SPECTATOR)
259 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
263 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
267 for(i = 0; i < MAX_TEAMSCORE; ++i)
269 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
274 if (left.team < right.team)
280 void Scoreboard_UpdateTeamPos(entity Team)
283 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
285 SORT_SWAP(Team, ent);
287 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
289 SORT_SWAP(ent, Team);
293 void Cmd_Scoreboard_Help()
295 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command."));
296 LOG_INFO(_("Usage:"));
297 LOG_INFO("^2scoreboard_columns_set ^3default");
298 LOG_INFO(_("^2scoreboard_columns_set ^3field1 field2 ..."));
299 LOG_INFO(_("^2scoreboard_columns_set ^7without arguments reads the arguments from the cvar scoreboard_columns"));
300 LOG_INFO(_(" ^5Note: ^7scoreboard_columns_set without arguments is executed on every map start"));
301 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields."));
302 LOG_INFO(_("The following field names are recognized (case insensitive):"));
305 LOG_INFO(strcat("^3name^7 ", _("Name of a player")));
306 LOG_INFO(strcat("^3nick^7 ", _("Name of a player")));
307 LOG_INFO(strcat("^3ping^7 ", _("Ping time")));
308 LOG_INFO(strcat("^3pl^7 ", _("Packet loss")));
309 LOG_INFO(strcat("^3elo^7 ", _("Player ELO")));
310 LOG_INFO(strcat("^3fps^7 ", _("Player FPS")));
311 LOG_INFO(strcat("^3kills^7 ", _("Number of kills")));
312 LOG_INFO(strcat("^3deaths^7 ", _("Number of deaths")));
313 LOG_INFO(strcat("^3suicides^7 ", _("Number of suicides")));
314 LOG_INFO(strcat("^3frags^7 ", _("kills - suicides")));
315 LOG_INFO(strcat("^3teamkills^7 ", _("Number of teamkills")));
316 LOG_INFO(strcat("^3kd^7 ", _("The kill-death ratio")));
317 LOG_INFO(strcat("^3dmg^7 ", _("The total damage done")));
318 LOG_INFO(strcat("^3dmgtaken^7 ", _("The total damage taken")));
319 LOG_INFO(strcat("^3sum^7 ", _("kills - deaths")));
320 LOG_INFO(strcat("^3caps^7 ", _("How often a flag (CTF) or a key (KeyHunt) was captured")));
321 LOG_INFO(strcat("^3pickups^7 ", _("How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up")));
322 LOG_INFO(strcat("^3captime^7 ", _("Time of fastest cap (CTF)")));
323 LOG_INFO(strcat("^3fckills^7 ", _("Number of flag carrier kills")));
324 LOG_INFO(strcat("^3returns^7 ", _("Number of flag returns")));
325 LOG_INFO(strcat("^3drops^7 ", _("Number of flag drops")));
326 LOG_INFO(strcat("^3lives^7 ", _("Number of lives (LMS)")));
327 LOG_INFO(strcat("^3rank^7 ", _("Player rank")));
328 LOG_INFO(strcat("^3pushes^7 ", _("Number of players pushed into void")));
329 LOG_INFO(strcat("^3destroyed^7 ", _("Number of keys destroyed by pushing them into void")));
330 LOG_INFO(strcat("^3kckills^7 ", _("Number of keys carrier kills")));
331 LOG_INFO(strcat("^3losses^7 ", _("Number of times a key was lost")));
332 LOG_INFO(strcat("^3laps^7 ", _("Number of laps finished (race/cts)")));
333 LOG_INFO(strcat("^3time^7 ", _("Total time raced (race/cts)")));
334 LOG_INFO(strcat("^3fastest^7 ", _("Time of fastest lap (race/cts)")));
335 LOG_INFO(strcat("^3ticks^7 ", _("Number of ticks (DOM)")));
336 LOG_INFO(strcat("^3takes^7 ", _("Number of domination points taken (DOM)")));
337 LOG_INFO(strcat("^3bckills^7 ", _("Number of ball carrier kills")));
338 LOG_INFO(strcat("^3bctime^7 ", _("Total amount of time holding the ball in Keepaway")));
339 LOG_INFO(strcat("^3score^7 ", _("Total score")));
342 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
343 "of game types, then a slash, to make the field show up only in these\n"
344 "or in all but these game types. You can also specify 'all' as a\n"
345 "field to show all fields available for the current game mode."));
348 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
349 "include/exclude ALL teams/noteams game modes."));
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\n"
354 "right of the vertical bar aligned to the right."));
355 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
356 "other gamemodes except DM."));
359 // NOTE: adding a gametype with ? to not warn for an optional field
360 // make sure it's excluded in a previous exclusive rule, if any
361 // otherwise the previous exclusive rule warns anyway
362 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
363 #define SCOREBOARD_DEFAULT_COLUMNS \
364 "ping pl fps name |" \
365 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
366 " -teams,lms/deaths +ft,tdm/deaths" \
368 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
369 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
370 " +tdm,ft,dom,ons,as/teamkills"\
371 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
372 " +ctf/pickups +ctf/fckills +ctf/returns +ctf/caps +ons/takes +ons/caps" \
373 " +lms/lives +lms/rank" \
374 " +kh/kckills +kh/losses +kh/caps" \
375 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
376 " +as/objectives +nb/faults +nb/goals" \
377 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
378 " +dom/ticks +dom/takes" \
379 " -lms,rc,cts,inv,nb/score"
381 void Cmd_Scoreboard_SetFields(int argc)
386 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
390 return; // do nothing, we don't know gametype and scores yet
392 // sbt_fields uses strunzone on the titles!
393 if(!sbt_field_title[0])
394 for(i = 0; i < MAX_SBT_FIELDS; ++i)
395 sbt_field_title[i] = strzone("(null)");
397 // TODO: re enable with gametype dependant cvars?
398 if(argc < 3) // no arguments provided
399 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
402 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
406 if(argv(2) == "default")
407 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
408 else if(argv(2) == "all")
410 string s = "ping pl name |"; // scores without a label
411 FOREACH(Scores, true, {
413 if(it != ps_secondary)
414 if(scores_label(it) != "")
415 s = strcat(s, " ", scores_label(it));
417 if(ps_secondary != ps_primary)
418 s = strcat(s, " ", scores_label(ps_secondary));
419 s = strcat(s, " ", scores_label(ps_primary));
420 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
427 hud_fontsize = HUD_GetFontsize("hud_fontsize");
429 for(i = 1; i < argc - 1; ++i)
432 bool nocomplain = false;
433 if(substring(str, 0, 1) == "?")
436 str = substring(str, 1, strlen(str) - 1);
439 slash = strstrofs(str, "/", 0);
442 pattern = substring(str, 0, slash);
443 str = substring(str, slash + 1, strlen(str) - (slash + 1));
445 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
449 strcpy(sbt_field_title[sbt_num_fields], 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 strcpy(sbt_field_title[sbt_num_fields], "|");
541 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
542 sbt_field[sbt_num_fields] = SP_SEPARATOR;
544 LOG_INFO("fixed missing field '|'");
548 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_secondary)));
549 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
550 sbt_field[sbt_num_fields] = ps_secondary;
552 LOG_INFOF("fixed missing field '%s'", scores_label(ps_secondary));
556 strcpy(sbt_field_title[sbt_num_fields], TranslateScoresLabel(scores_label(ps_primary)));
557 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
558 sbt_field[sbt_num_fields] = ps_primary;
560 LOG_INFOF("fixed missing field '%s'", scores_label(ps_primary));
564 sbt_field[sbt_num_fields] = SP_END;
568 vector sbt_field_rgb;
569 string sbt_field_icon0;
570 string sbt_field_icon1;
571 string sbt_field_icon2;
572 vector sbt_field_icon0_rgb;
573 vector sbt_field_icon1_rgb;
574 vector sbt_field_icon2_rgb;
575 string Scoreboard_GetName(entity pl)
577 if(ready_waiting && pl.ready)
579 sbt_field_icon0 = "gfx/scoreboard/player_ready";
583 int f = entcs_GetClientColors(pl.sv_entnum);
585 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
586 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
587 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
588 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
589 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
592 return entcs_GetName(pl.sv_entnum);
594 string Scoreboard_GetField(entity pl, PlayerScoreField field)
596 float tmp, num, denom;
599 sbt_field_rgb = '1 1 1';
600 sbt_field_icon0 = "";
601 sbt_field_icon1 = "";
602 sbt_field_icon2 = "";
603 sbt_field_icon0_rgb = '1 1 1';
604 sbt_field_icon1_rgb = '1 1 1';
605 sbt_field_icon2_rgb = '1 1 1';
610 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
611 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
615 tmp = max(0, min(220, f-80)) / 220;
616 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
622 f = pl.ping_packetloss;
623 tmp = pl.ping_movementloss;
624 if(f == 0 && tmp == 0)
626 str = ftos(ceil(f * 100));
628 str = strcat(str, "~", ftos(ceil(tmp * 100)));
629 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
630 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
634 return Scoreboard_GetName(pl);
637 f = pl.(scores(SP_KILLS));
638 f -= pl.(scores(SP_SUICIDES));
642 num = pl.(scores(SP_KILLS));
643 denom = pl.(scores(SP_DEATHS));
646 sbt_field_rgb = '0 1 0';
647 str = sprintf("%d", num);
648 } else if(num <= 0) {
649 sbt_field_rgb = '1 0 0';
650 str = sprintf("%.1f", num/denom);
652 str = sprintf("%.1f", num/denom);
656 f = pl.(scores(SP_KILLS));
657 f -= pl.(scores(SP_DEATHS));
660 sbt_field_rgb = '0 1 0';
662 sbt_field_rgb = '1 1 1';
664 sbt_field_rgb = '1 0 0';
670 float elo = pl.(scores(SP_ELO));
672 case -1: return "...";
673 case -2: return _("N/A");
674 default: return ftos(elo);
680 float fps = pl.(scores(SP_FPS));
683 sbt_field_rgb = '1 1 1';
684 return ((pl.ping == 0) ? _("N/A") : "..."); // if 0 ping, either connecting or bot (either case can't show proper score)
686 //sbt_field_rgb = HUD_Get_Num_Color(fps, 200);
687 sbt_field_rgb = '1 0 0' + '0 1 1' * (bound(0, fps, 60) / 60);
691 case SP_DMG: case SP_DMGTAKEN:
692 return sprintf("%.1f k", pl.(scores(field)) / 1000);
694 default: case SP_SCORE:
695 tmp = pl.(scores(field));
696 f = scores_flags(field);
697 if(field == ps_primary)
698 sbt_field_rgb = '1 1 0';
699 else if(field == ps_secondary)
700 sbt_field_rgb = '0 1 1';
702 sbt_field_rgb = '1 1 1';
703 return ScoreString(f, tmp);
708 float sbt_fixcolumnwidth_len;
709 float sbt_fixcolumnwidth_iconlen;
710 float sbt_fixcolumnwidth_marginlen;
712 string Scoreboard_FixColumnWidth(int i, string str)
718 sbt_fixcolumnwidth_iconlen = 0;
720 if(sbt_field_icon0 != "")
722 sz = draw_getimagesize(sbt_field_icon0);
724 if(sbt_fixcolumnwidth_iconlen < f)
725 sbt_fixcolumnwidth_iconlen = f;
728 if(sbt_field_icon1 != "")
730 sz = draw_getimagesize(sbt_field_icon1);
732 if(sbt_fixcolumnwidth_iconlen < f)
733 sbt_fixcolumnwidth_iconlen = f;
736 if(sbt_field_icon2 != "")
738 sz = draw_getimagesize(sbt_field_icon2);
740 if(sbt_fixcolumnwidth_iconlen < f)
741 sbt_fixcolumnwidth_iconlen = f;
744 if(sbt_fixcolumnwidth_iconlen != 0)
746 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
747 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
750 sbt_fixcolumnwidth_marginlen = 0;
752 if(sbt_field[i] == SP_NAME) // name gets all remaining space
755 float remaining_space = 0;
756 for(j = 0; j < sbt_num_fields; ++j)
758 if (sbt_field[i] != SP_SEPARATOR)
759 remaining_space += sbt_field_size[j] + hud_fontsize.x;
760 sbt_field_size[i] = panel_size.x - remaining_space;
762 if (sbt_fixcolumnwidth_iconlen != 0)
763 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
764 float namesize = panel_size.x - remaining_space;
765 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
766 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
768 max_namesize = vid_conwidth - remaining_space;
771 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
773 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
774 if(sbt_field_size[i] < f)
775 sbt_field_size[i] = f;
780 void Scoreboard_initFieldSizes()
782 for(int i = 0; i < sbt_num_fields; ++i)
784 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
785 Scoreboard_FixColumnWidth(i, "");
789 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
792 vector column_dim = eY * panel_size.y;
794 column_dim.y -= 1.25 * hud_fontsize.y;
795 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
796 pos.x += hud_fontsize.x * 0.5;
797 for(i = 0; i < sbt_num_fields; ++i)
799 if(sbt_field[i] == SP_SEPARATOR)
801 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
804 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
805 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
806 pos.x += column_dim.x;
808 if(sbt_field[i] == SP_SEPARATOR)
810 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
811 for(i = sbt_num_fields - 1; i > 0; --i)
813 if(sbt_field[i] == SP_SEPARATOR)
816 pos.x -= sbt_field_size[i];
821 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
822 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
825 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
826 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
827 pos.x -= hud_fontsize.x;
832 pos.y += 1.25 * hud_fontsize.y;
836 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
838 TC(bool, is_self); TC(int, pl_number);
840 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
842 vector h_pos = item_pos;
843 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
844 // alternated rows highlighting
846 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
847 else if((sbt_highlight) && (!(pl_number % 2)))
848 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
850 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
852 vector pos = item_pos;
853 pos.x += hud_fontsize.x * 0.5;
854 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
855 vector tmp = '0 0 0';
857 PlayerScoreField field;
858 for(i = 0; i < sbt_num_fields; ++i)
860 field = sbt_field[i];
861 if(field == SP_SEPARATOR)
864 if(is_spec && field != SP_NAME && field != SP_PING) {
865 pos.x += sbt_field_size[i] + hud_fontsize.x;
868 str = Scoreboard_GetField(pl, field);
869 str = Scoreboard_FixColumnWidth(i, str);
871 pos.x += sbt_field_size[i] + hud_fontsize.x;
873 if(field == SP_NAME) {
874 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
875 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
877 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
878 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
881 tmp.x = sbt_field_size[i] + hud_fontsize.x;
882 if(sbt_field_icon0 != "")
883 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
884 if(sbt_field_icon1 != "")
885 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
886 if(sbt_field_icon2 != "")
887 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
890 if(sbt_field[i] == SP_SEPARATOR)
892 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
893 for(i = sbt_num_fields-1; i > 0; --i)
895 field = sbt_field[i];
896 if(field == SP_SEPARATOR)
899 if(is_spec && field != SP_NAME && field != SP_PING) {
900 pos.x -= sbt_field_size[i] + hud_fontsize.x;
904 str = Scoreboard_GetField(pl, field);
905 str = Scoreboard_FixColumnWidth(i, str);
907 if(field == SP_NAME) {
908 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
909 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
911 tmp.x = sbt_fixcolumnwidth_len;
912 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
915 tmp.x = sbt_field_size[i];
916 if(sbt_field_icon0 != "")
917 drawpic(pos - tmp, sbt_field_icon0, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
918 if(sbt_field_icon1 != "")
919 drawpic(pos - tmp, sbt_field_icon1, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
920 if(sbt_field_icon2 != "")
921 drawpic(pos - tmp, sbt_field_icon2, vec2(hud_fontsize.x * sbt_fixcolumnwidth_iconlen, hud_fontsize.y), sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
922 pos.x -= sbt_field_size[i] + hud_fontsize.x;
927 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
930 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
933 vector h_pos = item_pos;
934 vector h_size = vec2(panel_size.x, hud_fontsize.y * 1.25);
936 bool complete = (this_team == NUM_SPECTATOR);
939 if((sbt_highlight) && (!(pl_number % 2)))
940 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
942 vector pos = item_pos;
943 pos.x += hud_fontsize.x * 0.5;
944 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
946 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
948 width_limit -= stringwidth("...", false, hud_fontsize);
949 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
950 static float max_name_width = 0;
953 float min_fieldsize = 0;
954 float fieldpadding = hud_fontsize.x * 0.25;
955 if(this_team == NUM_SPECTATOR)
957 if(autocvar_hud_panel_scoreboard_spectators_showping)
958 min_fieldsize = stringwidth("999", false, hud_fontsize);
960 else if(autocvar_hud_panel_scoreboard_others_showscore)
961 min_fieldsize = stringwidth("99", false, hud_fontsize);
962 for(i = 0; pl; pl = pl.sort_next)
964 if(pl.team != this_team)
970 if(this_team == NUM_SPECTATOR)
972 if(autocvar_hud_panel_scoreboard_spectators_showping)
973 field = Scoreboard_GetField(pl, SP_PING);
975 else if(autocvar_hud_panel_scoreboard_others_showscore)
976 field = Scoreboard_GetField(pl, SP_SCORE);
978 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
979 float column_width = stringwidth(str, true, hud_fontsize);
980 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
982 if(column_width > max_name_width)
983 max_name_width = column_width;
984 column_width = max_name_width;
988 fieldsize = stringwidth(field, false, hud_fontsize);
989 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
992 if(pos.x + column_width > width_limit)
997 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1002 pos.x = item_pos.x + hud_fontsize.x * 0.5;
1003 pos.y += hud_fontsize.y * 1.25;
1007 vector name_pos = pos;
1008 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
1009 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
1010 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1013 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
1014 h_size.y = hud_fontsize.y;
1015 vector field_pos = pos;
1016 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
1017 field_pos.x += column_width - h_size.x;
1019 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1020 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
1021 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1023 pos.x += column_width;
1024 pos.x += hud_fontsize.x;
1026 return vec2(item_pos.x, item_pos.y + i * hud_fontsize.y * 1.25);
1029 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1031 int max_players = 999;
1032 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1034 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1037 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1038 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1039 height /= team_count;
1042 height -= panel_bg_padding * 2; // - padding
1043 max_players = floor(height / (hud_fontsize.y * 1.25));
1044 if(max_players <= 1)
1046 if(max_players == tm.team_size)
1051 entity me = playerslots[current_player];
1053 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1054 panel_size.y += panel_bg_padding * 2;
1057 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1058 if(panel.current_panel_bg != "0")
1059 end_pos.y += panel_bg_border * 2;
1061 if(panel_bg_padding)
1063 panel_pos += '1 1 0' * panel_bg_padding;
1064 panel_size -= '2 2 0' * panel_bg_padding;
1068 vector tmp = vec2(panel_size.x, 1.25 * hud_fontsize.y);
1072 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1074 pos.y += 1.25 * hud_fontsize.y;
1077 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1079 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1082 // print header row and highlight columns
1083 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1085 // fill the table and draw the rows
1086 bool is_self = false;
1087 bool self_shown = false;
1089 for(pl = players.sort_next; pl; pl = pl.sort_next)
1091 if(pl.team != tm.team)
1093 if(i == max_players - 2 && pl != me)
1095 if(!self_shown && me.team == tm.team)
1097 Scoreboard_DrawItem(pos, rgb, me, true, i);
1099 pos.y += 1.25 * hud_fontsize.y;
1103 if(i >= max_players - 1)
1105 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1108 is_self = (pl.sv_entnum == current_player);
1109 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1112 pos.y += 1.25 * hud_fontsize.y;
1116 panel_size.x += panel_bg_padding * 2; // restore initial width
1120 bool Scoreboard_WouldDraw()
1122 if (MUTATOR_CALLHOOK(DrawScoreboard))
1124 else if (QuickMenu_IsOpened())
1126 else if (HUD_Radar_Clickable())
1128 else if (scoreboard_showscores)
1130 else if (intermission == 1)
1132 else if (intermission == 2)
1134 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1136 else if (scoreboard_showscores_force)
1141 float average_accuracy;
1142 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1144 WepSet weapons_stat = WepSet_GetFromStat();
1145 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1146 int disownedcnt = 0;
1148 FOREACH(Weapons, it != WEP_Null, {
1149 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1151 WepSet set = it.m_wepset;
1152 if(it.spawnflags & WEP_TYPE_OTHER)
1157 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1159 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1166 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1167 if (weapon_cnt <= 0) return pos;
1170 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1172 int columnns = ceil(weapon_cnt / rows);
1174 float weapon_height = 29;
1175 float height = hud_fontsize.y + weapon_height;
1177 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1178 pos.y += 1.25 * hud_fontsize.y;
1179 if(panel.current_panel_bg != "0")
1180 pos.y += panel_bg_border;
1183 panel_size.y = height * rows;
1184 panel_size.y += panel_bg_padding * 2;
1187 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1188 if(panel.current_panel_bg != "0")
1189 end_pos.y += panel_bg_border * 2;
1191 if(panel_bg_padding)
1193 panel_pos += '1 1 0' * panel_bg_padding;
1194 panel_size -= '2 2 0' * panel_bg_padding;
1198 vector tmp = panel_size;
1200 float weapon_width = tmp.x / columnns / rows;
1203 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1207 // column highlighting
1208 for (int i = 0; i < columnns; ++i)
1210 drawfill(pos + eX * weapon_width * rows * i, vec2(weapon_width * rows, height * rows), '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1213 for (int i = 0; i < rows; ++i)
1214 drawfill(pos + eY * (weapon_height + height * i), vec2(tmp.x, hud_fontsize.y), rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1217 average_accuracy = 0;
1218 int weapons_with_stats = 0;
1220 pos.x += weapon_width / 2;
1222 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1225 Accuracy_LoadColors();
1227 float oldposx = pos.x;
1231 FOREACH(Weapons, it != WEP_Null, {
1232 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1234 WepSet set = it.m_wepset;
1235 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1237 if (it.spawnflags & WEP_TYPE_OTHER)
1241 if (weapon_stats >= 0)
1242 weapon_alpha = sbt_fg_alpha;
1244 weapon_alpha = 0.2 * sbt_fg_alpha;
1247 drawpic_aspect_skin(tmpos, it.model2, vec2(weapon_width, weapon_height), '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1249 if (weapon_stats >= 0) {
1250 weapons_with_stats += 1;
1251 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1254 s = sprintf("%d%%", weapon_stats * 100);
1257 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1259 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1260 rgb = Accuracy_GetColor(weapon_stats);
1262 drawstring(tmpos + vec2(padding, weapon_height), s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1264 tmpos.x += weapon_width * rows;
1265 pos.x += weapon_width * rows;
1266 if (rows == 2 && column == columnns - 1) {
1274 if (weapons_with_stats)
1275 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1277 panel_size.x += panel_bg_padding * 2; // restore initial width
1281 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1283 pos.x += hud_fontsize.x * 0.25;
1284 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1285 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1286 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1288 pos.y += hud_fontsize.y;
1293 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1294 float stat_secrets_found, stat_secrets_total;
1295 float stat_monsters_killed, stat_monsters_total;
1299 // get monster stats
1300 stat_monsters_killed = STAT(MONSTERS_KILLED);
1301 stat_monsters_total = STAT(MONSTERS_TOTAL);
1303 // get secrets stats
1304 stat_secrets_found = STAT(SECRETS_FOUND);
1305 stat_secrets_total = STAT(SECRETS_TOTAL);
1307 // get number of rows
1308 if(stat_secrets_total)
1310 if(stat_monsters_total)
1313 // if no rows, return
1317 // draw table header
1318 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1319 pos.y += 1.25 * hud_fontsize.y;
1320 if(panel.current_panel_bg != "0")
1321 pos.y += panel_bg_border;
1324 panel_size.y = hud_fontsize.y * rows;
1325 panel_size.y += panel_bg_padding * 2;
1328 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1329 if(panel.current_panel_bg != "0")
1330 end_pos.y += panel_bg_border * 2;
1332 if(panel_bg_padding)
1334 panel_pos += '1 1 0' * panel_bg_padding;
1335 panel_size -= '2 2 0' * panel_bg_padding;
1339 vector tmp = panel_size;
1342 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1345 if(stat_monsters_total)
1347 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1348 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1352 if(stat_secrets_total)
1354 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1355 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1358 panel_size.x += panel_bg_padding * 2; // restore initial width
1363 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1366 RANKINGS_RECEIVED_CNT = 0;
1367 for (i=RANKINGS_CNT-1; i>=0; --i)
1369 ++RANKINGS_RECEIVED_CNT;
1371 if (RANKINGS_RECEIVED_CNT == 0)
1374 vector hl_rgb = rgb + '0.5 0.5 0.5';
1376 pos.y += hud_fontsize.y;
1377 drawstring(pos + eX * panel_bg_padding, ((gametype == MAPINFO_TYPE_CTF) ? _("Capture time rankings") : _("Rankings")), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1378 pos.y += 1.25 * hud_fontsize.y;
1379 if(panel.current_panel_bg != "0")
1380 pos.y += panel_bg_border;
1385 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1387 float f = stringwidth(ColorTranslateRGB(grecordholder[i]), true, hud_fontsize);
1392 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1394 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1398 float ranksize = 3 * hud_fontsize.x;
1399 float timesize = 5 * hud_fontsize.x;
1400 vector columnsize = vec2(ranksize + timesize + namesize + hud_fontsize.x, 1.25 * hud_fontsize.y);
1401 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1402 columns = min(columns, RANKINGS_RECEIVED_CNT);
1404 // expand name column to fill the entire row
1405 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1406 namesize += available_space;
1407 columnsize.x += available_space;
1409 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1410 panel_size.y += panel_bg_padding * 2;
1414 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1415 if(panel.current_panel_bg != "0")
1416 end_pos.y += panel_bg_border * 2;
1418 if(panel_bg_padding)
1420 panel_pos += '1 1 0' * panel_bg_padding;
1421 panel_size -= '2 2 0' * panel_bg_padding;
1427 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1429 vector text_ofs = vec2(0.5 * hud_fontsize.x, (1.25 - 1) / 2 * hud_fontsize.y); // center text vertically
1431 int column = 0, j = 0;
1432 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1439 if(strdecolorize(grecordholder[i]) == strdecolorize(entcs_GetName(player_localnum)))
1440 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1441 else if(!((j + column) & 1) && sbt_highlight)
1442 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1444 str = count_ordinal(i+1);
1445 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1446 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1447 str = ColorTranslateRGB(grecordholder[i]);
1449 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1450 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1452 pos.y += 1.25 * hud_fontsize.y;
1454 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1458 pos.x += panel_size.x / columns;
1459 pos.y = panel_pos.y;
1463 panel_size.x += panel_bg_padding * 2; // restore initial width
1467 void Scoreboard_Draw()
1469 if(!autocvar__hud_configure)
1471 if(!hud_draw_maximized) return;
1473 // frametime checks allow to toggle the scoreboard even when the game is paused
1474 if(scoreboard_active) {
1475 if(hud_configure_menu_open == 1)
1476 scoreboard_fade_alpha = 1;
1477 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1478 if (scoreboard_fadeinspeed && frametime)
1479 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1481 scoreboard_fade_alpha = 1;
1482 if(hud_fontsize_str != autocvar_hud_fontsize)
1484 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1485 Scoreboard_initFieldSizes();
1486 strcpy(hud_fontsize_str, autocvar_hud_fontsize);
1490 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1491 if (scoreboard_fadeoutspeed && frametime)
1492 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1494 scoreboard_fade_alpha = 0;
1497 if (!scoreboard_fade_alpha)
1501 scoreboard_fade_alpha = 0;
1503 if (autocvar_hud_panel_scoreboard_dynamichud)
1506 HUD_Scale_Disable();
1508 if(scoreboard_fade_alpha <= 0)
1510 panel_fade_alpha *= scoreboard_fade_alpha;
1511 HUD_Panel_LoadCvars();
1513 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1514 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1515 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1516 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1517 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1518 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1520 // don't overlap with con_notify
1521 if(!autocvar__hud_configure)
1522 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1524 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1525 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1526 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1527 panel_size.x = fixed_scoreboard_width;
1529 Scoreboard_UpdatePlayerTeams();
1531 vector pos = panel_pos;
1537 vector sb_heading_fontsize;
1538 sb_heading_fontsize = hud_fontsize * 2;
1539 draw_beginBoldFont();
1540 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1543 pos.y += sb_heading_fontsize.y;
1544 if(panel.current_panel_bg != "0")
1545 pos.y += panel_bg_border;
1547 // Draw the scoreboard
1548 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1551 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1555 vector panel_bg_color_save = panel_bg_color;
1556 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1557 if(panel.current_panel_bg != "0")
1558 team_score_baseoffset.x -= panel_bg_border;
1559 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1561 if(tm.team == NUM_SPECTATOR)
1566 draw_beginBoldFont();
1567 vector rgb = Team_ColorRGB(tm.team);
1568 str = ftos(tm.(teamscores(ts_primary)));
1569 str_pos = pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5);
1570 drawstring(str_pos, str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1572 if(ts_primary != ts_secondary)
1574 str = ftos(tm.(teamscores(ts_secondary)));
1575 str_pos = pos + team_score_baseoffset - vec2(stringwidth(str, false, hud_fontsize), hud_fontsize.y * 1.5);
1576 drawstring(str_pos, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1579 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1580 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1581 else if(panel_bg_color_team > 0)
1582 panel_bg_color = rgb * panel_bg_color_team;
1584 panel_bg_color = rgb;
1585 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1587 panel_bg_color = panel_bg_color_save;
1591 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1592 if(tm.team != NUM_SPECTATOR)
1594 // display it anyway
1595 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1598 bool show_accuracy = (gametype != MAPINFO_TYPE_CTS && gametype != MAPINFO_TYPE_RACE && gametype != MAPINFO_TYPE_NEXBALL);
1600 if (show_accuracy && autocvar_hud_panel_scoreboard_accuracy && !warmup_stage)
1601 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1603 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE || (autocvar_hud_panel_scoreboard_ctf_leaderboard && gametype == MAPINFO_TYPE_CTF && STAT(CTF_SHOWLEADERBOARD))) {
1604 if(race_speedaward) {
1605 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);
1606 pos.y += 1.25 * hud_fontsize.y;
1608 if(race_speedaward_alltimebest) {
1609 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);
1610 pos.y += 1.25 * hud_fontsize.y;
1612 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1615 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1618 for(pl = players.sort_next; pl; pl = pl.sort_next)
1620 if(pl.team == NUM_SPECTATOR)
1622 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1623 if(tm.team == NUM_SPECTATOR)
1625 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1626 draw_beginBoldFont();
1627 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1629 pos.y += 1.25 * hud_fontsize.y;
1631 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1632 pos.y += 1.25 * hud_fontsize.y;
1638 // Print info string
1640 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1641 tl = STAT(TIMELIMIT);
1642 fl = STAT(FRAGLIMIT);
1643 ll = STAT(LEADLIMIT);
1644 if(gametype == MAPINFO_TYPE_LMS)
1647 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1652 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1656 str = strcat(str, _(" or"));
1659 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1660 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1661 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1662 TranslateScoresLabel(teamscores_label(ts_primary))));
1666 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1667 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1668 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1669 TranslateScoresLabel(scores_label(ps_primary))));
1674 if(tl > 0 || fl > 0)
1675 str = strcat(str, _(" or"));
1678 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1679 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1680 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1681 TranslateScoresLabel(teamscores_label(ts_primary))));
1685 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1686 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1687 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1688 TranslateScoresLabel(scores_label(ps_primary))));
1693 pos.y += 1.2 * hud_fontsize.y;
1694 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1696 // print information about respawn status
1697 float respawn_time = STAT(RESPAWN_TIME);
1701 if(respawn_time < 0)
1703 // a negative number means we are awaiting respawn, time value is still the same
1704 respawn_time *= -1; // remove mark now that we checked it
1706 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1707 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1709 str = sprintf(_("^1Respawning in ^3%s^1..."),
1710 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1711 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1713 count_seconds(ceil(respawn_time - time))
1717 else if(time < respawn_time)
1719 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1720 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1721 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1723 count_seconds(ceil(respawn_time - time))
1727 else if(time >= respawn_time)
1728 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1730 pos.y += 1.2 * hud_fontsize.y;
1731 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1734 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;