1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/net_linked.qh>
7 #include <common/mapinfo.qh>
8 #include <common/minigames/cl_minigames.qh>
9 #include <common/stats.qh>
10 #include <common/teams.qh>
14 const int MAX_SBT_FIELDS = MAX_SCORE;
16 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
17 float sbt_field_size[MAX_SBT_FIELDS + 1];
18 string sbt_field_title[MAX_SBT_FIELDS + 1];
21 string autocvar_hud_fontsize;
22 string hud_fontsize_str;
27 float sbt_fg_alpha_self;
29 float sbt_highlight_alpha;
30 float sbt_highlight_alpha_self;
32 // provide basic panel cvars to old clients
33 // TODO remove them after a future release (0.8.2+)
34 noref string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
35 noref string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
36 noref string autocvar_hud_panel_scoreboard_bg = "border_default";
37 noref string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
38 noref string autocvar_hud_panel_scoreboard_bg_color_team = "";
39 noref string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
40 noref string autocvar_hud_panel_scoreboard_bg_border = "";
41 noref string autocvar_hud_panel_scoreboard_bg_padding = "";
43 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
44 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
45 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
46 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
47 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
49 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
50 bool autocvar_hud_panel_scoreboard_table_highlight = true;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
52 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
53 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
54 float autocvar_hud_panel_scoreboard_namesize = 15;
56 bool autocvar_hud_panel_scoreboard_accuracy = true;
57 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
58 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
60 bool autocvar_hud_panel_scoreboard_dynamichud = false;
62 float autocvar_hud_panel_scoreboard_maxheight = 0.6;
63 bool autocvar_hud_panel_scoreboard_others_showscore = true;
64 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
65 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
66 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
69 void drawstringright(vector, string, vector, vector, float, float);
70 void drawstringcenter(vector, string, vector, vector, float, float);
72 // wrapper to put all possible scores titles through gettext
73 string TranslateScoresLabel(string l)
77 case "bckills": return CTX(_("SCO^bckills"));
78 case "bctime": return CTX(_("SCO^bctime"));
79 case "caps": return CTX(_("SCO^caps"));
80 case "captime": return CTX(_("SCO^captime"));
81 case "deaths": return CTX(_("SCO^deaths"));
82 case "destroyed": return CTX(_("SCO^destroyed"));
83 case "dmg": return CTX(_("SCO^damage"));
84 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
85 case "drops": return CTX(_("SCO^drops"));
86 case "faults": return CTX(_("SCO^faults"));
87 case "fckills": return CTX(_("SCO^fckills"));
88 case "goals": return CTX(_("SCO^goals"));
89 case "kckills": return CTX(_("SCO^kckills"));
90 case "kdratio": return CTX(_("SCO^kdratio"));
91 case "kd": return CTX(_("SCO^k/d"));
92 case "kdr": return CTX(_("SCO^kdr"));
93 case "kills": return CTX(_("SCO^kills"));
94 case "laps": return CTX(_("SCO^laps"));
95 case "lives": return CTX(_("SCO^lives"));
96 case "losses": return CTX(_("SCO^losses"));
97 case "name": return CTX(_("SCO^name"));
98 case "sum": return CTX(_("SCO^sum"));
99 case "nick": return CTX(_("SCO^nick"));
100 case "objectives": return CTX(_("SCO^objectives"));
101 case "pickups": return CTX(_("SCO^pickups"));
102 case "ping": return CTX(_("SCO^ping"));
103 case "pl": return CTX(_("SCO^pl"));
104 case "pushes": return CTX(_("SCO^pushes"));
105 case "rank": return CTX(_("SCO^rank"));
106 case "returns": return CTX(_("SCO^returns"));
107 case "revivals": return CTX(_("SCO^revivals"));
108 case "rounds": return CTX(_("SCO^rounds won"));
109 case "score": return CTX(_("SCO^score"));
110 case "suicides": return CTX(_("SCO^suicides"));
111 case "takes": return CTX(_("SCO^takes"));
112 case "ticks": return CTX(_("SCO^ticks"));
117 void Scoreboard_InitScores()
121 ps_primary = ps_secondary = NULL;
122 ts_primary = ts_secondary = -1;
123 FOREACH(Scores, true, {
124 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
125 if(f == SFL_SORT_PRIO_PRIMARY)
127 if(f == SFL_SORT_PRIO_SECONDARY)
130 if(ps_secondary == NULL)
131 ps_secondary = ps_primary;
133 for(i = 0; i < MAX_TEAMSCORE; ++i)
135 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
136 if(f == SFL_SORT_PRIO_PRIMARY)
138 if(f == SFL_SORT_PRIO_SECONDARY)
141 if(ts_secondary == -1)
142 ts_secondary = ts_primary;
144 Cmd_Scoreboard_SetFields(0);
147 float SetTeam(entity pl, float Team);
149 void Scoreboard_UpdatePlayerTeams()
154 for(pl = players.sort_next; pl; pl = pl.sort_next)
157 Team = entcs_GetScoreTeam(pl.sv_entnum);
158 if(SetTeam(pl, Team))
161 Scoreboard_UpdatePlayerPos(pl);
165 pl = players.sort_next;
170 print(strcat("PNUM: ", ftos(num), "\n"));
175 int Scoreboard_CompareScore(int vl, int vr, int f)
177 TC(int, vl); TC(int, vr); TC(int, f);
178 if(f & SFL_ZERO_IS_WORST)
180 if(vl == 0 && vr != 0)
182 if(vl != 0 && vr == 0)
186 return IS_INCREASING(f);
188 return IS_DECREASING(f);
192 float Scoreboard_ComparePlayerScores(entity left, entity right)
195 vl = entcs_GetTeam(left.sv_entnum);
196 vr = entcs_GetTeam(right.sv_entnum);
208 if(vl == NUM_SPECTATOR)
210 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
212 if(!left.gotscores && right.gotscores)
217 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
221 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
225 FOREACH(Scores, true, {
226 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
227 if (r >= 0) return r;
230 if (left.sv_entnum < right.sv_entnum)
236 void Scoreboard_UpdatePlayerPos(entity player)
239 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
241 SORT_SWAP(player, ent);
243 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
245 SORT_SWAP(ent, player);
249 float Scoreboard_CompareTeamScores(entity left, entity right)
253 if(left.team == NUM_SPECTATOR)
255 if(right.team == NUM_SPECTATOR)
258 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
262 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
266 for(i = 0; i < MAX_TEAMSCORE; ++i)
268 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
273 if (left.team < right.team)
279 void Scoreboard_UpdateTeamPos(entity Team)
282 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
284 SORT_SWAP(Team, ent);
286 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
288 SORT_SWAP(ent, Team);
292 void Cmd_Scoreboard_Help()
294 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
295 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
296 LOG_INFO(_("Usage:\n"));
297 LOG_INFO(_("^2scoreboard_columns_set default\n"));
298 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
299 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
300 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
303 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
304 LOG_INFO(_("^3ping^7 Ping time\n"));
305 LOG_INFO(_("^3pl^7 Packet loss\n"));
306 LOG_INFO(_("^3elo^7 Player ELO\n"));
307 LOG_INFO(_("^3kills^7 Number of kills\n"));
308 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
309 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
310 LOG_INFO(_("^3frags^7 kills - suicides\n"));
311 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
312 LOG_INFO(_("^3dmg^7 The total damage done\n"));
313 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
314 LOG_INFO(_("^3sum^7 frags - deaths\n"));
315 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
316 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
317 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
318 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
319 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
320 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
321 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
322 LOG_INFO(_("^3rank^7 Player rank\n"));
323 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
324 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
325 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
326 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
327 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
328 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
329 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
330 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
331 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
332 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
333 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
334 LOG_INFO(_("^3score^7 Total score\n"));
337 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
338 "of game types, then a slash, to make the field show up only in these\n"
339 "or in all but these game types. You can also specify 'all' as a\n"
340 "field to show all fields available for the current game mode.\n\n"));
342 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
343 "include/exclude ALL teams/noteams game modes.\n\n"));
345 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
346 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
347 "right of the vertical bar aligned to the right.\n"));
348 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
349 "other gamemodes except DM.\n"));
352 // NOTE: adding a gametype with ? to not warn for an optional field
353 // make sure it's excluded in a previous exclusive rule, if any
354 // otherwise the previous exclusive rule warns anyway
355 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
356 #define SCOREBOARD_DEFAULT_COLUMNS \
358 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
359 " -teams,lms/deaths +ft,tdm/deaths" \
360 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
361 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
362 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
363 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
364 " +lms/lives +lms/rank" \
365 " +kh/caps +kh/pushes +kh/destroyed" \
366 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
367 " +as/objectives +nb/faults +nb/goals" \
368 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
369 " -lms,rc,cts,inv,nb/score"
371 void Cmd_Scoreboard_SetFields(int argc)
376 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
380 return; // do nothing, we don't know gametype and scores yet
382 // sbt_fields uses strunzone on the titles!
383 if(!sbt_field_title[0])
384 for(i = 0; i < MAX_SBT_FIELDS; ++i)
385 sbt_field_title[i] = strzone("(null)");
387 // TODO: re enable with gametype dependant cvars?
388 if(argc < 3) // no arguments provided
389 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
392 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
396 if(argv(2) == "default")
397 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
398 else if(argv(2) == "all")
401 s = "ping pl name |";
402 FOREACH(Scores, true, {
404 if(it != ps_secondary)
405 if(scores_label(it) != "")
406 s = strcat(s, " ", scores_label(it));
408 if(ps_secondary != ps_primary)
409 s = strcat(s, " ", scores_label(ps_secondary));
410 s = strcat(s, " ", scores_label(ps_primary));
411 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
418 hud_fontsize = HUD_GetFontsize("hud_fontsize");
420 for(i = 1; i < argc - 1; ++i)
426 if(substring(str, 0, 1) == "?")
429 str = substring(str, 1, strlen(str) - 1);
432 slash = strstrofs(str, "/", 0);
435 pattern = substring(str, 0, slash);
436 str = substring(str, slash + 1, strlen(str) - (slash + 1));
438 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
442 strunzone(sbt_field_title[sbt_num_fields]);
443 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
444 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
445 str = strtolower(str);
450 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
451 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
452 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
453 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
454 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
455 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
456 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
457 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
458 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
461 FOREACH(Scores, true, {
462 if (str == strtolower(scores_label(it))) {
464 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
474 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
478 sbt_field[sbt_num_fields] = j;
481 if(j == ps_secondary)
482 have_secondary = true;
487 if(sbt_num_fields >= MAX_SBT_FIELDS)
491 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
493 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
494 have_secondary = true;
495 if(ps_primary == ps_secondary)
496 have_secondary = true;
497 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
499 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
503 strunzone(sbt_field_title[sbt_num_fields]);
504 for(i = sbt_num_fields; i > 0; --i)
506 sbt_field_title[i] = sbt_field_title[i-1];
507 sbt_field_size[i] = sbt_field_size[i-1];
508 sbt_field[i] = sbt_field[i-1];
510 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
511 sbt_field[0] = SP_NAME;
513 LOG_INFO("fixed missing field 'name'\n");
517 strunzone(sbt_field_title[sbt_num_fields]);
518 for(i = sbt_num_fields; i > 1; --i)
520 sbt_field_title[i] = sbt_field_title[i-1];
521 sbt_field_size[i] = sbt_field_size[i-1];
522 sbt_field[i] = sbt_field[i-1];
524 sbt_field_title[1] = strzone("|");
525 sbt_field[1] = SP_SEPARATOR;
526 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
528 LOG_INFO("fixed missing field '|'\n");
531 else if(!have_separator)
533 strunzone(sbt_field_title[sbt_num_fields]);
534 sbt_field_title[sbt_num_fields] = strzone("|");
535 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
536 sbt_field[sbt_num_fields] = SP_SEPARATOR;
538 LOG_INFO("fixed missing field '|'\n");
542 strunzone(sbt_field_title[sbt_num_fields]);
543 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
544 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
545 sbt_field[sbt_num_fields] = ps_secondary;
547 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
551 strunzone(sbt_field_title[sbt_num_fields]);
552 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
553 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
554 sbt_field[sbt_num_fields] = ps_primary;
556 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
560 sbt_field[sbt_num_fields] = SP_END;
564 vector sbt_field_rgb;
565 string sbt_field_icon0;
566 string sbt_field_icon1;
567 string sbt_field_icon2;
568 vector sbt_field_icon0_rgb;
569 vector sbt_field_icon1_rgb;
570 vector sbt_field_icon2_rgb;
571 string Scoreboard_GetField(entity pl, PlayerScoreField field)
573 float tmp, num, denom;
576 sbt_field_rgb = '1 1 1';
577 sbt_field_icon0 = "";
578 sbt_field_icon1 = "";
579 sbt_field_icon2 = "";
580 sbt_field_icon0_rgb = '1 1 1';
581 sbt_field_icon1_rgb = '1 1 1';
582 sbt_field_icon2_rgb = '1 1 1';
587 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
588 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
592 tmp = max(0, min(220, f-80)) / 220;
593 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
599 f = pl.ping_packetloss;
600 tmp = pl.ping_movementloss;
601 if(f == 0 && tmp == 0)
603 str = ftos(ceil(f * 100));
605 str = strcat(str, "~", ftos(ceil(tmp * 100)));
606 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
607 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
611 if(ready_waiting && pl.ready)
613 sbt_field_icon0 = "gfx/scoreboard/player_ready";
617 f = entcs_GetClientColors(pl.sv_entnum);
619 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
620 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
621 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
622 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
623 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
626 return entcs_GetName(pl.sv_entnum);
629 f = pl.(scores(SP_KILLS));
630 f -= pl.(scores(SP_SUICIDES));
634 num = pl.(scores(SP_KILLS));
635 denom = pl.(scores(SP_DEATHS));
638 sbt_field_rgb = '0 1 0';
639 str = sprintf("%d", num);
640 } else if(num <= 0) {
641 sbt_field_rgb = '1 0 0';
642 str = sprintf("%.1f", num/denom);
644 str = sprintf("%.1f", num/denom);
648 f = pl.(scores(SP_KILLS));
649 f -= pl.(scores(SP_DEATHS));
652 sbt_field_rgb = '0 1 0';
654 sbt_field_rgb = '1 1 1';
656 sbt_field_rgb = '1 0 0';
662 float elo = pl.(scores(SP_ELO));
664 case -1: return "...";
665 case -2: return _("N/A");
666 default: return ftos(elo);
670 case SP_DMG: case SP_DMGTAKEN:
671 return sprintf("%.1f k", pl.(scores(field)) / 1000);
673 default: case SP_SCORE:
674 tmp = pl.(scores(field));
675 f = scores_flags(field);
676 if(field == ps_primary)
677 sbt_field_rgb = '1 1 0';
678 else if(field == ps_secondary)
679 sbt_field_rgb = '0 1 1';
681 sbt_field_rgb = '1 1 1';
682 return ScoreString(f, tmp);
687 float sbt_fixcolumnwidth_len;
688 float sbt_fixcolumnwidth_iconlen;
689 float sbt_fixcolumnwidth_marginlen;
691 string Scoreboard_FixColumnWidth(int i, string str)
697 sbt_fixcolumnwidth_iconlen = 0;
699 if(sbt_field_icon0 != "")
701 sz = draw_getimagesize(sbt_field_icon0);
703 if(sbt_fixcolumnwidth_iconlen < f)
704 sbt_fixcolumnwidth_iconlen = f;
707 if(sbt_field_icon1 != "")
709 sz = draw_getimagesize(sbt_field_icon1);
711 if(sbt_fixcolumnwidth_iconlen < f)
712 sbt_fixcolumnwidth_iconlen = f;
715 if(sbt_field_icon2 != "")
717 sz = draw_getimagesize(sbt_field_icon2);
719 if(sbt_fixcolumnwidth_iconlen < f)
720 sbt_fixcolumnwidth_iconlen = f;
723 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
725 if(sbt_fixcolumnwidth_iconlen != 0)
726 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
728 sbt_fixcolumnwidth_marginlen = 0;
730 if(sbt_field[i] == SP_NAME) // name gets all remaining space
733 float remaining_space = 0;
734 for(j = 0; j < sbt_num_fields; ++j)
736 if (sbt_field[i] != SP_SEPARATOR)
737 remaining_space += sbt_field_size[j] + hud_fontsize.x;
738 sbt_field_size[i] = panel_size.x - remaining_space;
740 if (sbt_fixcolumnwidth_iconlen != 0)
741 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
742 float namesize = panel_size.x - remaining_space;
743 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
744 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
746 max_namesize = vid_conwidth - remaining_space;
749 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
751 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
752 if(sbt_field_size[i] < f)
753 sbt_field_size[i] = f;
758 void Scoreboard_initFieldSizes()
760 for(int i = 0; i < sbt_num_fields; ++i)
762 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
763 Scoreboard_FixColumnWidth(i, "");
767 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
770 vector column_dim = eY * panel_size.y;
772 column_dim.y -= 1.25 * hud_fontsize.y;
773 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
774 pos.x += hud_fontsize.x * 0.5;
775 for(i = 0; i < sbt_num_fields; ++i)
777 if(sbt_field[i] == SP_SEPARATOR)
779 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
782 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
783 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
784 pos.x += column_dim.x;
786 if(sbt_field[i] == SP_SEPARATOR)
788 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
789 for(i = sbt_num_fields - 1; i > 0; --i)
791 if(sbt_field[i] == SP_SEPARATOR)
794 pos.x -= sbt_field_size[i];
799 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
800 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
803 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
804 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
805 pos.x -= hud_fontsize.x;
810 pos.y += 1.25 * hud_fontsize.y;
814 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
816 TC(bool, is_self); TC(int, pl_number);
818 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
820 vector h_pos = item_pos;
821 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
822 // alternated rows highlighting
824 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
825 else if((sbt_highlight) && (!(pl_number % 2)))
826 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
828 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
830 vector pos = item_pos;
831 pos.x += hud_fontsize.x * 0.5;
832 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
833 vector tmp = '0 0 0';
835 PlayerScoreField field;
836 for(i = 0; i < sbt_num_fields; ++i)
838 field = sbt_field[i];
839 if(field == SP_SEPARATOR)
842 if(is_spec && field != SP_NAME && field != SP_PING) {
843 pos.x += sbt_field_size[i] + hud_fontsize.x;
846 str = Scoreboard_GetField(pl, field);
847 str = Scoreboard_FixColumnWidth(i, str);
849 pos.x += sbt_field_size[i] + hud_fontsize.x;
851 if(field == SP_NAME) {
852 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
853 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
855 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
856 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
859 tmp.x = sbt_field_size[i] + hud_fontsize.x;
860 if(sbt_field_icon0 != "")
861 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
862 if(sbt_field_icon1 != "")
863 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
864 if(sbt_field_icon2 != "")
865 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
868 if(sbt_field[i] == SP_SEPARATOR)
870 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
871 for(i = sbt_num_fields-1; i > 0; --i)
873 field = sbt_field[i];
874 if(field == SP_SEPARATOR)
877 if(is_spec && field != SP_NAME && field != SP_PING) {
878 pos.x -= sbt_field_size[i] + hud_fontsize.x;
882 str = Scoreboard_GetField(pl, field);
883 str = Scoreboard_FixColumnWidth(i, str);
885 if(field == SP_NAME) {
886 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
887 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
889 tmp.x = sbt_fixcolumnwidth_len;
890 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
893 tmp.x = sbt_field_size[i];
894 if(sbt_field_icon0 != "")
895 drawpic(pos - tmp, sbt_field_icon0, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
896 if(sbt_field_icon1 != "")
897 drawpic(pos - tmp, sbt_field_icon1, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon1_rgb, fg_alpha, DRAWFLAG_NORMAL);
898 if(sbt_field_icon2 != "")
899 drawpic(pos - tmp, sbt_field_icon2, eY * hud_fontsize.y + eX * hud_fontsize.x * sbt_fixcolumnwidth_iconlen, sbt_field_icon2_rgb, fg_alpha, DRAWFLAG_NORMAL);
900 pos.x -= sbt_field_size[i] + hud_fontsize.x;
905 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
908 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
911 vector h_pos = item_pos;
912 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
914 bool complete = (this_team == NUM_SPECTATOR);
917 if((sbt_highlight) && (!(pl_number % 2)))
918 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
920 vector pos = item_pos;
921 pos.x += hud_fontsize.x * 0.5;
922 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
924 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
926 width_limit -= stringwidth("...", false, hud_fontsize);
927 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
928 static float max_name_width = 0;
931 float min_fieldsize = 0;
932 float fieldpadding = hud_fontsize.x * 0.25;
933 if(this_team == NUM_SPECTATOR)
935 if(autocvar_hud_panel_scoreboard_spectators_showping)
936 min_fieldsize = stringwidth("999", false, hud_fontsize);
938 else if(autocvar_hud_panel_scoreboard_others_showscore)
939 min_fieldsize = stringwidth("99", false, hud_fontsize);
940 for(i = 0; pl; pl = pl.sort_next)
942 if(pl.team != this_team)
948 if(this_team == NUM_SPECTATOR)
950 if(autocvar_hud_panel_scoreboard_spectators_showping)
951 field = Scoreboard_GetField(pl, SP_PING);
953 else if(autocvar_hud_panel_scoreboard_others_showscore)
954 field = Scoreboard_GetField(pl, SP_SCORE);
956 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
957 float column_width = stringwidth(str, true, hud_fontsize);
958 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
960 if(column_width > max_name_width)
961 max_name_width = column_width;
962 column_width = max_name_width;
966 fieldsize = stringwidth(field, false, hud_fontsize);
967 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
970 if(pos.x + column_width > width_limit)
975 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
980 pos.x = item_pos.x + hud_fontsize.x * 0.5;
981 pos.y += hud_fontsize.y * 1.25;
985 vector name_pos = pos;
986 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
987 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
988 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
991 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
992 h_size.y = hud_fontsize.y;
993 vector field_pos = pos;
994 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
995 field_pos.x += column_width - h_size.x;
997 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
998 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
999 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1001 pos.x += column_width;
1002 pos.x += hud_fontsize.x;
1004 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
1007 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1009 int max_players = 999;
1010 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1012 float height = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1015 height -= (panel_bg_padding * 2 + hud_fontsize.y * 1.25) * team_count; // - padding and header
1016 height -= hud_fontsize.y * (team_count - 1); // - spacing between tables
1017 height /= team_count;
1020 height -= panel_bg_padding * 2; // - padding
1021 max_players = floor(height / (hud_fontsize.y * 1.25));
1022 if(max_players <= 1)
1024 if(max_players == tm.team_size)
1029 entity me = playerslots[current_player];
1031 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1032 panel_size.y += panel_bg_padding * 2;
1035 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1036 if(panel.current_panel_bg != "0")
1037 end_pos.y += panel_bg_border * 2;
1039 if(panel_bg_padding)
1041 panel_pos += '1 1 0' * panel_bg_padding;
1042 panel_size -= '2 2 0' * panel_bg_padding;
1046 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1050 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1052 pos.y += 1.25 * hud_fontsize.y;
1055 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1057 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1060 // print header row and highlight columns
1061 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1063 // fill the table and draw the rows
1064 bool is_self = false;
1065 bool self_shown = false;
1067 for(pl = players.sort_next; pl; pl = pl.sort_next)
1069 if(pl.team != tm.team)
1071 if(i == max_players - 2 && pl != me)
1073 if(!self_shown && me.team == tm.team)
1075 Scoreboard_DrawItem(pos, rgb, me, true, i);
1077 pos.y += 1.25 * hud_fontsize.y;
1081 if(i >= max_players - 1)
1083 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1086 is_self = (pl.sv_entnum == current_player);
1087 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1090 pos.y += 1.25 * hud_fontsize.y;
1094 panel_size.x += panel_bg_padding * 2; // restore initial width
1098 bool Scoreboard_WouldDraw()
1100 if (MUTATOR_CALLHOOK(DrawScoreboard))
1102 else if (QuickMenu_IsOpened())
1104 else if (HUD_Radar_Clickable())
1106 else if (scoreboard_showscores)
1108 else if (intermission == 1)
1110 else if (intermission == 2)
1112 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1114 else if (scoreboard_showscores_force)
1119 float average_accuracy;
1120 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1122 WepSet weapons_stat = WepSet_GetFromStat();
1123 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1124 int disownedcnt = 0;
1126 FOREACH(Weapons, it != WEP_Null, {
1127 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1129 WepSet set = it.m_wepset;
1130 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1132 if (((it.spawnflags & WEP_FLAG_HIDDEN) || (it.spawnflags & WEP_FLAG_MUTATORBLOCKED)))
1139 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1140 if (weapon_cnt <= 0) return pos;
1143 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1145 int columnns = ceil(weapon_cnt / rows);
1147 float weapon_height = 29;
1148 float height = hud_fontsize.y + weapon_height;
1150 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1151 pos.y += 1.25 * hud_fontsize.y;
1152 if(panel.current_panel_bg != "0")
1153 pos.y += panel_bg_border;
1156 panel_size.y = height * rows;
1157 panel_size.y += panel_bg_padding * 2;
1160 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1161 if(panel.current_panel_bg != "0")
1162 end_pos.y += panel_bg_border * 2;
1164 if(panel_bg_padding)
1166 panel_pos += '1 1 0' * panel_bg_padding;
1167 panel_size -= '2 2 0' * panel_bg_padding;
1171 vector tmp = panel_size;
1173 float weapon_width = tmp.x / columnns / rows;
1176 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1180 // column highlighting
1181 for (int i = 0; i < columnns; ++i)
1183 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1186 for (int i = 0; i < rows; ++i)
1187 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1190 average_accuracy = 0;
1191 int weapons_with_stats = 0;
1193 pos.x += weapon_width / 2;
1195 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1198 Accuracy_LoadColors();
1200 float oldposx = pos.x;
1204 FOREACH(Weapons, it != WEP_Null, {
1205 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1207 WepSet set = it.m_wepset;
1208 if (weapon_stats < 0 && !((weapons_stat & set) || (weapons_inmap & set)))
1212 if (weapon_stats >= 0)
1213 weapon_alpha = sbt_fg_alpha;
1215 weapon_alpha = 0.2 * sbt_fg_alpha;
1218 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1220 if (weapon_stats >= 0) {
1221 weapons_with_stats += 1;
1222 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1225 s = sprintf("%d%%", weapon_stats * 100);
1228 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1230 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1231 rgb = Accuracy_GetColor(weapon_stats);
1233 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1235 tmpos.x += weapon_width * rows;
1236 pos.x += weapon_width * rows;
1237 if (rows == 2 && column == columnns - 1) {
1245 if (weapons_with_stats)
1246 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1248 panel_size.x += panel_bg_padding * 2; // restore initial width
1252 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1254 pos.x += hud_fontsize.x * 0.25;
1255 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1256 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1257 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1259 pos.y += hud_fontsize.y;
1264 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1265 float stat_secrets_found, stat_secrets_total;
1266 float stat_monsters_killed, stat_monsters_total;
1270 // get monster stats
1271 stat_monsters_killed = STAT(MONSTERS_KILLED);
1272 stat_monsters_total = STAT(MONSTERS_TOTAL);
1274 // get secrets stats
1275 stat_secrets_found = STAT(SECRETS_FOUND);
1276 stat_secrets_total = STAT(SECRETS_TOTAL);
1278 // get number of rows
1279 if(stat_secrets_total)
1281 if(stat_monsters_total)
1284 // if no rows, return
1288 // draw table header
1289 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1290 pos.y += 1.25 * hud_fontsize.y;
1291 if(panel.current_panel_bg != "0")
1292 pos.y += panel_bg_border;
1295 panel_size.y = hud_fontsize.y * rows;
1296 panel_size.y += panel_bg_padding * 2;
1299 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1300 if(panel.current_panel_bg != "0")
1301 end_pos.y += panel_bg_border * 2;
1303 if(panel_bg_padding)
1305 panel_pos += '1 1 0' * panel_bg_padding;
1306 panel_size -= '2 2 0' * panel_bg_padding;
1310 vector tmp = panel_size;
1313 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1316 if(stat_monsters_total)
1318 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1319 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1323 if(stat_secrets_total)
1325 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1326 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1329 panel_size.x += panel_bg_padding * 2; // restore initial width
1334 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1337 RANKINGS_RECEIVED_CNT = 0;
1338 for (i=RANKINGS_CNT-1; i>=0; --i)
1340 ++RANKINGS_RECEIVED_CNT;
1342 if (RANKINGS_RECEIVED_CNT == 0)
1345 vector hl_rgb = rgb + '0.5 0.5 0.5';
1347 pos.y += hud_fontsize.y;
1348 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1349 pos.y += 1.25 * hud_fontsize.y;
1350 if(panel.current_panel_bg != "0")
1351 pos.y += panel_bg_border;
1356 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1358 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1363 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1365 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1369 float ranksize = 3 * hud_fontsize.x;
1370 float timesize = 5 * hud_fontsize.x;
1371 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1372 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1373 columns = min(columns, RANKINGS_RECEIVED_CNT);
1375 // expand name column to fill the entire row
1376 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1377 namesize += available_space;
1378 columnsize.x += available_space;
1380 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1381 panel_size.y += panel_bg_padding * 2;
1385 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1386 if(panel.current_panel_bg != "0")
1387 end_pos.y += panel_bg_border * 2;
1389 if(panel_bg_padding)
1391 panel_pos += '1 1 0' * panel_bg_padding;
1392 panel_size -= '2 2 0' * panel_bg_padding;
1398 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1400 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1402 int column = 0, j = 0;
1403 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1410 if(grecordholder[i] == entcs_GetName(player_localnum))
1411 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1412 else if(!((j + column) & 1) && sbt_highlight)
1413 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1415 str = count_ordinal(i+1);
1416 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1417 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1418 str = grecordholder[i];
1420 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1421 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1423 pos.y += 1.25 * hud_fontsize.y;
1425 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1429 pos.x += panel_size.x / columns;
1430 pos.y = panel_pos.y;
1434 panel_size.x += panel_bg_padding * 2; // restore initial width
1438 void Scoreboard_Draw()
1440 if(!autocvar__hud_configure)
1442 if(!hud_draw_maximized) return;
1444 // frametime checks allow to toggle the scoreboard even when the game is paused
1445 if(scoreboard_active) {
1446 if(hud_configure_menu_open == 1)
1447 scoreboard_fade_alpha = 1;
1448 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1449 if (scoreboard_fadeinspeed && frametime)
1450 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1452 scoreboard_fade_alpha = 1;
1453 if(hud_fontsize_str != autocvar_hud_fontsize)
1455 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1456 Scoreboard_initFieldSizes();
1457 if(hud_fontsize_str)
1458 strunzone(hud_fontsize_str);
1459 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1463 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1464 if (scoreboard_fadeoutspeed && frametime)
1465 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1467 scoreboard_fade_alpha = 0;
1470 if (!scoreboard_fade_alpha)
1474 scoreboard_fade_alpha = 0;
1476 if (autocvar_hud_panel_scoreboard_dynamichud)
1479 HUD_Scale_Disable();
1481 if(scoreboard_fade_alpha <= 0)
1483 panel_fade_alpha *= scoreboard_fade_alpha;
1484 HUD_Panel_LoadCvars();
1486 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1487 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1488 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1489 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1490 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1491 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1493 // don't overlap with con_notify
1494 if(!autocvar__hud_configure)
1495 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1497 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1498 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1499 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1500 panel_size.x = fixed_scoreboard_width;
1502 Scoreboard_UpdatePlayerTeams();
1504 vector pos = panel_pos;
1509 vector sb_heading_fontsize;
1510 sb_heading_fontsize = hud_fontsize * 2;
1511 draw_beginBoldFont();
1512 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1515 pos.y += sb_heading_fontsize.y;
1516 if(panel.current_panel_bg != "0")
1517 pos.y += panel_bg_border;
1519 // Draw the scoreboard
1520 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1523 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1527 vector panel_bg_color_save = panel_bg_color;
1528 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1529 if(panel.current_panel_bg != "0")
1530 team_score_baseoffset.x -= panel_bg_border;
1531 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1533 if(tm.team == NUM_SPECTATOR)
1538 draw_beginBoldFont();
1539 vector rgb = Team_ColorRGB(tm.team);
1540 str = ftos(tm.(teamscores(ts_primary)));
1541 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1543 if(ts_primary != ts_secondary)
1545 str = ftos(tm.(teamscores(ts_secondary)));
1546 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize) + eY * hud_fontsize.y * 1.5, str, hud_fontsize, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1549 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1550 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1551 else if(panel_bg_color_team > 0)
1552 panel_bg_color = rgb * panel_bg_color_team;
1554 panel_bg_color = rgb;
1555 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1557 panel_bg_color = panel_bg_color_save;
1561 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1562 if(tm.team != NUM_SPECTATOR)
1564 // display it anyway
1565 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1568 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1569 if(race_speedaward) {
1570 drawcolorcodedstring(pos, sprintf(_("Speed award: %d%s ^7(%s^7)"), race_speedaward, race_speedaward_unit, race_speedaward_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1571 pos.y += 1.25 * hud_fontsize.y;
1573 if(race_speedaward_alltimebest) {
1574 drawcolorcodedstring(pos, sprintf(_("All-time fastest: %d%s ^7(%s^7)"), race_speedaward_alltimebest, race_speedaward_alltimebest_unit, race_speedaward_alltimebest_holder), hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1575 pos.y += 1.25 * hud_fontsize.y;
1577 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1579 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1580 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1582 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1585 for(pl = players.sort_next; pl; pl = pl.sort_next)
1587 if(pl.team == NUM_SPECTATOR)
1589 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1590 if(tm.team == NUM_SPECTATOR)
1592 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1593 draw_beginBoldFont();
1594 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1596 pos.y += 1.25 * hud_fontsize.y;
1598 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1599 pos.y += 1.25 * hud_fontsize.y;
1605 // Print info string
1607 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1608 tl = STAT(TIMELIMIT);
1609 fl = STAT(FRAGLIMIT);
1610 ll = STAT(LEADLIMIT);
1611 if(gametype == MAPINFO_TYPE_LMS)
1614 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1619 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1623 str = strcat(str, _(" or"));
1626 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1627 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1628 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1629 TranslateScoresLabel(teamscores_label(ts_primary))));
1633 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1634 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1635 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1636 TranslateScoresLabel(scores_label(ps_primary))));
1641 if(tl > 0 || fl > 0)
1642 str = strcat(str, _(" or"));
1645 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1646 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1647 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648 TranslateScoresLabel(teamscores_label(ts_primary))));
1652 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1653 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1654 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1655 TranslateScoresLabel(scores_label(ps_primary))));
1660 pos.y += 1.2 * hud_fontsize.y;
1661 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1663 // print information about respawn status
1664 float respawn_time = STAT(RESPAWN_TIME);
1668 if(respawn_time < 0)
1670 // a negative number means we are awaiting respawn, time value is still the same
1671 respawn_time *= -1; // remove mark now that we checked it
1673 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1674 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1676 str = sprintf(_("^1Respawning in ^3%s^1..."),
1677 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1678 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1680 count_seconds(ceil(respawn_time - time))
1684 else if(time < respawn_time)
1686 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1687 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1688 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1690 count_seconds(ceil(respawn_time - time))
1694 else if(time >= respawn_time)
1695 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1697 pos.y += 1.2 * hud_fontsize.y;
1698 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1701 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;