1 #include "scoreboard.qh"
3 #include "quickmenu.qh"
4 #include <common/ent_cs.qh>
5 #include <common/constants.qh>
6 #include <common/mapinfo.qh>
7 #include <common/minigames/cl_minigames.qh>
8 #include <common/stats.qh>
9 #include <common/teams.qh>
13 const int MAX_SBT_FIELDS = MAX_SCORE;
15 PlayerScoreField sbt_field[MAX_SBT_FIELDS + 1];
16 float sbt_field_size[MAX_SBT_FIELDS + 1];
17 string sbt_field_title[MAX_SBT_FIELDS + 1];
20 string autocvar_hud_fontsize;
21 string hud_fontsize_str;
26 float sbt_fg_alpha_self;
28 float sbt_highlight_alpha;
29 float sbt_highlight_alpha_self;
31 // provide basic panel cvars to old clients
32 // TODO remove them after a future release (0.8.2+)
33 string autocvar_hud_panel_scoreboard_pos = "0.150000 0.150000";
34 string autocvar_hud_panel_scoreboard_size = "0.700000 0.700000";
35 string autocvar_hud_panel_scoreboard_bg = "border_default";
36 string autocvar_hud_panel_scoreboard_bg_color = "0 0.3 0.5";
37 string autocvar_hud_panel_scoreboard_bg_color_team = "";
38 string autocvar_hud_panel_scoreboard_bg_alpha = "0.7";
39 string autocvar_hud_panel_scoreboard_bg_border = "";
40 string autocvar_hud_panel_scoreboard_bg_padding = "";
42 float autocvar_hud_panel_scoreboard_fadeinspeed = 10;
43 float autocvar_hud_panel_scoreboard_fadeoutspeed = 5;
44 float autocvar_hud_panel_scoreboard_respawntime_decimals = 1;
45 float autocvar_hud_panel_scoreboard_table_bg_alpha = 0;
46 float autocvar_hud_panel_scoreboard_table_bg_scale = 0.25;
47 float autocvar_hud_panel_scoreboard_table_fg_alpha = 0.9;
48 float autocvar_hud_panel_scoreboard_table_fg_alpha_self = 1;
49 bool autocvar_hud_panel_scoreboard_table_highlight = true;
50 float autocvar_hud_panel_scoreboard_table_highlight_alpha = 0.2;
51 float autocvar_hud_panel_scoreboard_table_highlight_alpha_self = 0.4;
52 float autocvar_hud_panel_scoreboard_bg_teams_color_team = 0;
53 float autocvar_hud_panel_scoreboard_namesize = 15;
55 bool autocvar_hud_panel_scoreboard_accuracy = true;
56 bool autocvar_hud_panel_scoreboard_accuracy_doublerows = false;
57 bool autocvar_hud_panel_scoreboard_accuracy_nocolors = false;
59 bool autocvar_hud_panel_scoreboard_dynamichud = false;
61 float autocvar_hud_panel_scoreboard_maxheight = 0.5;
62 bool autocvar_hud_panel_scoreboard_others_showscore = true;
63 bool autocvar_hud_panel_scoreboard_spectators_showping = true;
64 bool autocvar_hud_panel_scoreboard_spectators_aligned = false;
65 float autocvar_hud_panel_scoreboard_minwidth = 0.4;
68 void drawstringright(vector, string, vector, vector, float, float);
69 void drawstringcenter(vector, string, vector, vector, float, float);
71 // wrapper to put all possible scores titles through gettext
72 string TranslateScoresLabel(string l)
76 case "bckills": return CTX(_("SCO^bckills"));
77 case "bctime": return CTX(_("SCO^bctime"));
78 case "caps": return CTX(_("SCO^caps"));
79 case "captime": return CTX(_("SCO^captime"));
80 case "deaths": return CTX(_("SCO^deaths"));
81 case "destroyed": return CTX(_("SCO^destroyed"));
82 case "dmg": return CTX(_("SCO^damage"));
83 case "dmgtaken": return CTX(_("SCO^dmgtaken"));
84 case "drops": return CTX(_("SCO^drops"));
85 case "faults": return CTX(_("SCO^faults"));
86 case "fckills": return CTX(_("SCO^fckills"));
87 case "goals": return CTX(_("SCO^goals"));
88 case "kckills": return CTX(_("SCO^kckills"));
89 case "kdratio": return CTX(_("SCO^kdratio"));
90 case "kd": return CTX(_("SCO^k/d"));
91 case "kdr": return CTX(_("SCO^kdr"));
92 case "kills": return CTX(_("SCO^kills"));
93 case "laps": return CTX(_("SCO^laps"));
94 case "lives": return CTX(_("SCO^lives"));
95 case "losses": return CTX(_("SCO^losses"));
96 case "name": return CTX(_("SCO^name"));
97 case "sum": return CTX(_("SCO^sum"));
98 case "nick": return CTX(_("SCO^nick"));
99 case "objectives": return CTX(_("SCO^objectives"));
100 case "pickups": return CTX(_("SCO^pickups"));
101 case "ping": return CTX(_("SCO^ping"));
102 case "pl": return CTX(_("SCO^pl"));
103 case "pushes": return CTX(_("SCO^pushes"));
104 case "rank": return CTX(_("SCO^rank"));
105 case "returns": return CTX(_("SCO^returns"));
106 case "revivals": return CTX(_("SCO^revivals"));
107 case "rounds": return CTX(_("SCO^rounds won"));
108 case "score": return CTX(_("SCO^score"));
109 case "suicides": return CTX(_("SCO^suicides"));
110 case "takes": return CTX(_("SCO^takes"));
111 case "ticks": return CTX(_("SCO^ticks"));
116 void Scoreboard_InitScores()
120 ps_primary = ps_secondary = NULL;
121 ts_primary = ts_secondary = -1;
122 FOREACH(Scores, true, {
123 f = (scores_flags(it) & SFL_SORT_PRIO_MASK);
124 if(f == SFL_SORT_PRIO_PRIMARY)
126 if(f == SFL_SORT_PRIO_SECONDARY)
129 if(ps_secondary == NULL)
130 ps_secondary = ps_primary;
132 for(i = 0; i < MAX_TEAMSCORE; ++i)
134 f = (teamscores_flags(i) & SFL_SORT_PRIO_MASK);
135 if(f == SFL_SORT_PRIO_PRIMARY)
137 if(f == SFL_SORT_PRIO_SECONDARY)
140 if(ts_secondary == -1)
141 ts_secondary = ts_primary;
143 Cmd_Scoreboard_SetFields(0);
146 float SetTeam(entity pl, float Team);
148 void Scoreboard_UpdatePlayerTeams()
153 for(pl = players.sort_next; pl; pl = pl.sort_next)
156 Team = entcs_GetScoreTeam(pl.sv_entnum);
157 if(SetTeam(pl, Team))
160 Scoreboard_UpdatePlayerPos(pl);
164 pl = players.sort_next;
169 print(strcat("PNUM: ", ftos(num), "\n"));
174 int Scoreboard_CompareScore(int vl, int vr, int f)
176 TC(int, vl); TC(int, vr); TC(int, f);
177 if(f & SFL_ZERO_IS_WORST)
179 if(vl == 0 && vr != 0)
181 if(vl != 0 && vr == 0)
185 return IS_INCREASING(f);
187 return IS_DECREASING(f);
191 float Scoreboard_ComparePlayerScores(entity left, entity right)
194 vl = entcs_GetTeam(left.sv_entnum);
195 vr = entcs_GetTeam(right.sv_entnum);
207 if(vl == NUM_SPECTATOR)
209 // FIRST the one with scores (spectators), THEN the ones without (downloaders)
211 if(!left.gotscores && right.gotscores)
216 r = Scoreboard_CompareScore(left.scores(ps_primary), right.scores(ps_primary), scores_flags(ps_primary));
220 r = Scoreboard_CompareScore(left.scores(ps_secondary), right.scores(ps_secondary), scores_flags(ps_secondary));
224 FOREACH(Scores, true, {
225 r = Scoreboard_CompareScore(left.scores(it), right.scores(it), scores_flags(it));
226 if (r >= 0) return r;
229 if (left.sv_entnum < right.sv_entnum)
235 void Scoreboard_UpdatePlayerPos(entity player)
238 for(ent = player.sort_next; ent && Scoreboard_ComparePlayerScores(player, ent); ent = player.sort_next)
240 SORT_SWAP(player, ent);
242 for(ent = player.sort_prev; ent != players && Scoreboard_ComparePlayerScores(ent, player); ent = player.sort_prev)
244 SORT_SWAP(ent, player);
248 float Scoreboard_CompareTeamScores(entity left, entity right)
252 if(left.team == NUM_SPECTATOR)
254 if(right.team == NUM_SPECTATOR)
257 r = Scoreboard_CompareScore(left.teamscores(ts_primary), right.teamscores(ts_primary), teamscores_flags(ts_primary));
261 r = Scoreboard_CompareScore(left.teamscores(ts_secondary), right.teamscores(ts_secondary), teamscores_flags(ts_secondary));
265 for(i = 0; i < MAX_TEAMSCORE; ++i)
267 r = Scoreboard_CompareScore(left.teamscores(i), right.teamscores(i), teamscores_flags(i));
272 if (left.team < right.team)
278 void Scoreboard_UpdateTeamPos(entity Team)
281 for(ent = Team.sort_next; ent && Scoreboard_CompareTeamScores(Team, ent); ent = Team.sort_next)
283 SORT_SWAP(Team, ent);
285 for(ent = Team.sort_prev; ent != teams && Scoreboard_CompareTeamScores(ent, Team); ent = Team.sort_prev)
287 SORT_SWAP(ent, Team);
291 void Cmd_Scoreboard_Help()
293 LOG_INFO(_("You can modify the scoreboard using the ^2scoreboard_columns_set command.\n"));
294 LOG_INFO(_("^3|---------------------------------------------------------------|\n"));
295 LOG_INFO(_("Usage:\n"));
296 LOG_INFO(_("^2scoreboard_columns_set default\n"));
297 LOG_INFO(_("^2scoreboard_columns_set ^7field1 field2 ...\n"));
298 LOG_INFO(_("The following field names are recognized (case insensitive):\n"));
299 LOG_INFO(_("You can use a ^3|^7 to start the right-aligned fields.\n"));
302 LOG_INFO(_("^3name^7 or ^3nick^7 Name of a player\n"));
303 LOG_INFO(_("^3ping^7 Ping time\n"));
304 LOG_INFO(_("^3pl^7 Packet loss\n"));
305 LOG_INFO(_("^3elo^7 Player ELO\n"));
306 LOG_INFO(_("^3kills^7 Number of kills\n"));
307 LOG_INFO(_("^3deaths^7 Number of deaths\n"));
308 LOG_INFO(_("^3suicides^7 Number of suicides\n"));
309 LOG_INFO(_("^3frags^7 kills - suicides\n"));
310 LOG_INFO(_("^3kd^7 The kill-death ratio\n"));
311 LOG_INFO(_("^3dmg^7 The total damage done\n"));
312 LOG_INFO(_("^3dmgtaken^7 The total damage taken\n"));
313 LOG_INFO(_("^3sum^7 frags - deaths\n"));
314 LOG_INFO(_("^3caps^7 How often a flag (CTF) or a key (KeyHunt) was captured\n"));
315 LOG_INFO(_("^3pickups^7 How often a flag (CTF) or a key (KeyHunt) or a ball (Keepaway) was picked up\n"));
316 LOG_INFO(_("^3captime^7 Time of fastest cap (CTF)\n"));
317 LOG_INFO(_("^3fckills^7 Number of flag carrier kills\n"));
318 LOG_INFO(_("^3returns^7 Number of flag returns\n"));
319 LOG_INFO(_("^3drops^7 Number of flag drops\n"));
320 LOG_INFO(_("^3lives^7 Number of lives (LMS)\n"));
321 LOG_INFO(_("^3rank^7 Player rank\n"));
322 LOG_INFO(_("^3pushes^7 Number of players pushed into void\n"));
323 LOG_INFO(_("^3destroyed^7 Number of keys destroyed by pushing them into void\n"));
324 LOG_INFO(_("^3kckills^7 Number of keys carrier kills\n"));
325 LOG_INFO(_("^3losses^7 Number of times a key was lost\n"));
326 LOG_INFO(_("^3laps^7 Number of laps finished (race/cts)\n"));
327 LOG_INFO(_("^3time^7 Total time raced (race/cts)\n"));
328 LOG_INFO(_("^3fastest^7 Time of fastest lap (race/cts)\n"));
329 LOG_INFO(_("^3ticks^7 Number of ticks (DOM)\n"));
330 LOG_INFO(_("^3takes^7 Number of domination points taken (DOM)\n"));
331 LOG_INFO(_("^3bckills^7 Number of ball carrier kills\n"));
332 LOG_INFO(_("^3bctime^7 Total amount of time holding the ball in Keepaway\n"));
333 LOG_INFO(_("^3score^7 Total score\n"));
336 LOG_INFO(_("Before a field you can put a + or - sign, then a comma separated list\n"
337 "of game types, then a slash, to make the field show up only in these\n"
338 "or in all but these game types. You can also specify 'all' as a\n"
339 "field to show all fields available for the current game mode.\n\n"));
341 LOG_INFO(_("The special game type names 'teams' and 'noteams' can be used to\n"
342 "include/exclude ALL teams/noteams game modes.\n\n"));
344 LOG_INFO(_("Example: scoreboard_columns_set name ping pl | +ctf/field3 -dm/field4\n"));
345 LOG_INFO(_("will display name, ping and pl aligned to the left, and the fields\n"
346 "right of the vertical bar aligned to the right.\n"));
347 LOG_INFO(_("'field3' will only be shown in CTF, and 'field4' will be shown in all\n"
348 "other gamemodes except DM.\n"));
351 // NOTE: adding a gametype with ? to not warn for an optional field
352 // make sure it's excluded in a previous exclusive rule, if any
353 // otherwise the previous exclusive rule warns anyway
354 // e.g. -teams,rc,cts,lms/kills ?+rc/kills
355 #define SCOREBOARD_DEFAULT_COLUMNS \
357 " -teams,rc,cts,inv,lms/kills +ft,tdm/kills ?+rc,inv/kills" \
358 " -teams,lms/deaths +ft,tdm/deaths" \
359 " -teams,lms,rc,cts,inv,ka/suicides +ft,tdm/suicides ?+rc,inv/suicides" \
360 " -cts,dm,tdm,ka,ft/frags" /* tdm already has this in "score" */ \
361 " -rc,cts,nb/dmg -rc,cts,nb/dmgtaken" \
362 " +ctf/caps +ctf/pickups +ctf/fckills +ctf/returns +ons/caps +ons/takes" \
363 " +lms/lives +lms/rank" \
364 " +kh/caps +kh/pushes +kh/destroyed" \
365 " ?+rc/laps ?+rc/time +rc,cts/fastest" \
366 " +as/objectives +nb/faults +nb/goals" \
367 " +ka/pickups +ka/bckills +ka/bctime +ft/revivals" \
368 " -lms,rc,cts,inv,nb/score"
370 void Cmd_Scoreboard_SetFields(int argc)
375 bool have_name = false, have_primary = false, have_secondary = false, have_separator = false;
379 return; // do nothing, we don't know gametype and scores yet
381 // sbt_fields uses strunzone on the titles!
382 if(!sbt_field_title[0])
383 for(i = 0; i < MAX_SBT_FIELDS; ++i)
384 sbt_field_title[i] = strzone("(null)");
386 // TODO: re enable with gametype dependant cvars?
387 if(argc < 3) // no arguments provided
388 argc = tokenizebyseparator(strcat("0 1 ", autocvar_scoreboard_columns), " ");
391 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
395 if(argv(2) == "default")
396 argc = tokenizebyseparator(strcat("0 1 ", SCOREBOARD_DEFAULT_COLUMNS), " ");
397 else if(argv(2) == "all")
400 s = "ping pl name |";
401 FOREACH(Scores, true, {
403 if(it != ps_secondary)
404 if(scores_label(it) != "")
405 s = strcat(s, " ", scores_label(it));
407 if(ps_secondary != ps_primary)
408 s = strcat(s, " ", scores_label(ps_secondary));
409 s = strcat(s, " ", scores_label(ps_primary));
410 argc = tokenizebyseparator(strcat("0 1 ", s), " ");
417 hud_fontsize = HUD_GetFontsize("hud_fontsize");
419 for(i = 1; i < argc - 1; ++i)
425 if(substring(str, 0, 1) == "?")
428 str = substring(str, 1, strlen(str) - 1);
431 slash = strstrofs(str, "/", 0);
434 pattern = substring(str, 0, slash);
435 str = substring(str, slash + 1, strlen(str) - (slash + 1));
437 if (!isGametypeInFilter(gametype, teamplay, false, pattern))
441 strunzone(sbt_field_title[sbt_num_fields]);
442 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(str));
443 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
444 str = strtolower(str);
449 case "ping": sbt_field[sbt_num_fields] = SP_PING; break;
450 case "pl": sbt_field[sbt_num_fields] = SP_PL; break;
451 case "kd": case "kdr": case "kdratio": sbt_field[sbt_num_fields] = SP_KDRATIO; break;
452 case "sum": case "diff": case "k-d": sbt_field[sbt_num_fields] = SP_SUM; break;
453 case "name": case "nick": sbt_field[sbt_num_fields] = SP_NAME; have_name = true; break;
454 case "|": sbt_field[sbt_num_fields] = SP_SEPARATOR; have_separator = true; break;
455 case "elo": sbt_field[sbt_num_fields] = SP_ELO; break;
456 case "dmg": case "damage": sbt_field[sbt_num_fields] = SP_DMG; break;
457 case "dmgtaken": case "damagetaken": sbt_field[sbt_num_fields] = SP_DMGTAKEN; break;
460 FOREACH(Scores, true, {
461 if (str == strtolower(scores_label(it))) {
463 goto found; // sorry, but otherwise fteqcc -O3 miscompiles this and warns about "unreachable code"
473 LOG_INFOF("^1Error:^7 Unknown score field: '%s'\n", str);
477 sbt_field[sbt_num_fields] = j;
480 if(j == ps_secondary)
481 have_secondary = true;
486 if(sbt_num_fields >= MAX_SBT_FIELDS)
490 if(scores_flags(ps_primary) & SFL_ALLOW_HIDE)
492 if(scores_flags(ps_secondary) & SFL_ALLOW_HIDE)
493 have_secondary = true;
494 if(ps_primary == ps_secondary)
495 have_secondary = true;
496 missing = (!have_primary) + (!have_secondary) + (!have_separator) + (!have_name);
498 if(sbt_num_fields + missing < MAX_SBT_FIELDS)
502 strunzone(sbt_field_title[sbt_num_fields]);
503 for(i = sbt_num_fields; i > 0; --i)
505 sbt_field_title[i] = sbt_field_title[i-1];
506 sbt_field_size[i] = sbt_field_size[i-1];
507 sbt_field[i] = sbt_field[i-1];
509 sbt_field_title[0] = strzone(TranslateScoresLabel("name"));
510 sbt_field[0] = SP_NAME;
512 LOG_INFO("fixed missing field 'name'\n");
516 strunzone(sbt_field_title[sbt_num_fields]);
517 for(i = sbt_num_fields; i > 1; --i)
519 sbt_field_title[i] = sbt_field_title[i-1];
520 sbt_field_size[i] = sbt_field_size[i-1];
521 sbt_field[i] = sbt_field[i-1];
523 sbt_field_title[1] = strzone("|");
524 sbt_field[1] = SP_SEPARATOR;
525 sbt_field_size[1] = stringwidth("|", false, hud_fontsize);
527 LOG_INFO("fixed missing field '|'\n");
530 else if(!have_separator)
532 strunzone(sbt_field_title[sbt_num_fields]);
533 sbt_field_title[sbt_num_fields] = strzone("|");
534 sbt_field_size[sbt_num_fields] = stringwidth("|", false, hud_fontsize);
535 sbt_field[sbt_num_fields] = SP_SEPARATOR;
537 LOG_INFO("fixed missing field '|'\n");
541 strunzone(sbt_field_title[sbt_num_fields]);
542 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_secondary)));
543 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
544 sbt_field[sbt_num_fields] = ps_secondary;
546 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_secondary));
550 strunzone(sbt_field_title[sbt_num_fields]);
551 sbt_field_title[sbt_num_fields] = strzone(TranslateScoresLabel(scores_label(ps_primary)));
552 sbt_field_size[sbt_num_fields] = stringwidth(sbt_field_title[sbt_num_fields], false, hud_fontsize);
553 sbt_field[sbt_num_fields] = ps_primary;
555 LOG_INFOF("fixed missing field '%s'\n", scores_label(ps_primary));
559 sbt_field[sbt_num_fields] = SP_END;
563 vector sbt_field_rgb;
564 string sbt_field_icon0;
565 string sbt_field_icon1;
566 string sbt_field_icon2;
567 vector sbt_field_icon0_rgb;
568 vector sbt_field_icon1_rgb;
569 vector sbt_field_icon2_rgb;
570 string Scoreboard_GetField(entity pl, PlayerScoreField field)
572 float tmp, num, denom;
575 sbt_field_rgb = '1 1 1';
576 sbt_field_icon0 = "";
577 sbt_field_icon1 = "";
578 sbt_field_icon2 = "";
579 sbt_field_icon0_rgb = '1 1 1';
580 sbt_field_icon1_rgb = '1 1 1';
581 sbt_field_icon2_rgb = '1 1 1';
586 return "\xE2\x96\xB6\xE2\x96\xB6\xE2\x96\xB6"; // >>> sign using U+25B6
587 //str = getplayerkeyvalue(pl.sv_entnum, "ping");
591 tmp = max(0, min(220, f-80)) / 220;
592 sbt_field_rgb = '1 1 1' - '0 1 1' * tmp;
598 f = pl.ping_packetloss;
599 tmp = pl.ping_movementloss;
600 if(f == 0 && tmp == 0)
602 str = ftos(ceil(f * 100));
604 str = strcat(str, "~", ftos(ceil(tmp * 100)));
605 tmp = bound(0, f / 0.2 + tmp / 0.04, 1); // 20% is REALLY BAD pl
606 sbt_field_rgb = '1 0.5 0.5' - '0 0.5 0.5' * tmp;
610 if(ready_waiting && pl.ready)
612 sbt_field_icon0 = "gfx/scoreboard/player_ready";
616 f = entcs_GetClientColors(pl.sv_entnum);
618 sbt_field_icon0 = "gfx/scoreboard/playercolor_base";
619 sbt_field_icon1 = "gfx/scoreboard/playercolor_shirt";
620 sbt_field_icon1_rgb = colormapPaletteColor(floor(f / 16), 0);
621 sbt_field_icon2 = "gfx/scoreboard/playercolor_pants";
622 sbt_field_icon2_rgb = colormapPaletteColor(f % 16, 1);
625 return entcs_GetName(pl.sv_entnum);
628 f = pl.(scores(SP_KILLS));
629 f -= pl.(scores(SP_SUICIDES));
633 num = pl.(scores(SP_KILLS));
634 denom = pl.(scores(SP_DEATHS));
637 sbt_field_rgb = '0 1 0';
638 str = sprintf("%d", num);
639 } else if(num <= 0) {
640 sbt_field_rgb = '1 0 0';
641 str = sprintf("%.1f", num/denom);
643 str = sprintf("%.1f", num/denom);
647 f = pl.(scores(SP_KILLS));
648 f -= pl.(scores(SP_DEATHS));
651 sbt_field_rgb = '0 1 0';
653 sbt_field_rgb = '1 1 1';
655 sbt_field_rgb = '1 0 0';
661 float elo = pl.(scores(SP_ELO));
663 case -1: return "...";
664 case -2: return _("N/A");
665 default: return ftos(elo);
669 case SP_DMG: case SP_DMGTAKEN:
670 return sprintf("%.1f k", pl.(scores(field)) / 1000);
673 tmp = pl.(scores(field));
674 f = scores_flags(field);
675 if(field == ps_primary)
676 sbt_field_rgb = '1 1 0';
677 else if(field == ps_secondary)
678 sbt_field_rgb = '0 1 1';
680 sbt_field_rgb = '1 1 1';
681 return ScoreString(f, tmp);
686 float sbt_fixcolumnwidth_len;
687 float sbt_fixcolumnwidth_iconlen;
688 float sbt_fixcolumnwidth_marginlen;
690 string Scoreboard_FixColumnWidth(int i, string str)
696 sbt_fixcolumnwidth_iconlen = 0;
698 if(sbt_field_icon0 != "")
700 sz = draw_getimagesize(sbt_field_icon0);
702 if(sbt_fixcolumnwidth_iconlen < f)
703 sbt_fixcolumnwidth_iconlen = f;
706 if(sbt_field_icon1 != "")
708 sz = draw_getimagesize(sbt_field_icon1);
710 if(sbt_fixcolumnwidth_iconlen < f)
711 sbt_fixcolumnwidth_iconlen = f;
714 if(sbt_field_icon2 != "")
716 sz = draw_getimagesize(sbt_field_icon2);
718 if(sbt_fixcolumnwidth_iconlen < f)
719 sbt_fixcolumnwidth_iconlen = f;
722 sbt_fixcolumnwidth_iconlen *= hud_fontsize.y / hud_fontsize.x; // fix icon aspect
724 if(sbt_fixcolumnwidth_iconlen != 0)
725 sbt_fixcolumnwidth_marginlen = stringwidth(" ", false, hud_fontsize);
727 sbt_fixcolumnwidth_marginlen = 0;
729 if(sbt_field[i] == SP_NAME) // name gets all remaining space
732 float remaining_space = 0;
733 for(j = 0; j < sbt_num_fields; ++j)
735 if (sbt_field[i] != SP_SEPARATOR)
736 remaining_space += sbt_field_size[j] + hud_fontsize.x;
737 sbt_field_size[i] = panel_size.x - remaining_space;
739 if (sbt_fixcolumnwidth_iconlen != 0)
740 remaining_space += sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
741 float namesize = panel_size.x - remaining_space;
742 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
743 sbt_fixcolumnwidth_len = stringwidth(str, true, hud_fontsize);
745 max_namesize = vid_conwidth - remaining_space;
748 sbt_fixcolumnwidth_len = stringwidth(str, false, hud_fontsize);
750 f = sbt_fixcolumnwidth_len + sbt_fixcolumnwidth_marginlen + sbt_fixcolumnwidth_iconlen * hud_fontsize.x;
751 if(sbt_field_size[i] < f)
752 sbt_field_size[i] = f;
757 void Scoreboard_initFieldSizes()
759 for(int i = 0; i < sbt_num_fields; ++i)
761 sbt_field_size[i] = stringwidth(sbt_field_title[i], false, hud_fontsize);
762 Scoreboard_FixColumnWidth(i, "");
766 vector Scoreboard_DrawHeader(vector pos, vector rgb, bool other_players)
769 vector column_dim = eY * panel_size.y;
771 column_dim.y -= 1.25 * hud_fontsize.y;
772 vector text_offset = eY * (1.25 - 1) / 2 * hud_fontsize.y;
773 pos.x += hud_fontsize.x * 0.5;
774 for(i = 0; i < sbt_num_fields; ++i)
776 if(sbt_field[i] == SP_SEPARATOR)
778 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
781 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
782 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
783 pos.x += column_dim.x;
785 if(sbt_field[i] == SP_SEPARATOR)
787 pos.x = panel_pos.x + panel_size.x - hud_fontsize.x * 0.5;
788 for(i = sbt_num_fields - 1; i > 0; --i)
790 if(sbt_field[i] == SP_SEPARATOR)
793 pos.x -= sbt_field_size[i];
798 column_dim.x = sbt_field_size[i] + hud_fontsize.x;
799 drawfill(pos - eX * hud_fontsize.x * 0.5, column_dim, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
802 text_offset.x = sbt_field_size[i] - stringwidth(sbt_field_title[i], false, hud_fontsize);
803 drawstring(pos + text_offset, sbt_field_title[i], hud_fontsize, rgb * 1.5, sbt_fg_alpha, DRAWFLAG_NORMAL);
804 pos.x -= hud_fontsize.x;
809 pos.y += 1.25 * hud_fontsize.y;
813 void Scoreboard_DrawItem(vector item_pos, vector rgb, entity pl, bool is_self, int pl_number)
815 TC(bool, is_self); TC(int, pl_number);
817 bool is_spec = (entcs_GetTeam(pl.sv_entnum) == NUM_SPECTATOR);
819 vector h_pos = item_pos;
820 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
821 // alternated rows highlighting
823 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
824 else if((sbt_highlight) && (!(pl_number % 2)))
825 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
827 float fg_alpha = (is_self ? sbt_fg_alpha_self : sbt_fg_alpha);
829 vector pos = item_pos;
830 pos.x += hud_fontsize.x * 0.5;
831 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
832 vector tmp = '0 0 0';
834 PlayerScoreField field;
835 for(i = 0; i < sbt_num_fields; ++i)
837 field = sbt_field[i];
838 if(field == SP_SEPARATOR)
841 if(is_spec && field != SP_NAME && field != SP_PING) {
842 pos.x += sbt_field_size[i] + hud_fontsize.x;
845 str = Scoreboard_GetField(pl, field);
846 str = Scoreboard_FixColumnWidth(i, str);
848 pos.x += sbt_field_size[i] + hud_fontsize.x;
850 if(field == SP_NAME) {
851 tmp.x = sbt_field_size[i] - hud_fontsize.x * sbt_fixcolumnwidth_iconlen - sbt_fixcolumnwidth_marginlen + hud_fontsize.x;
852 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
854 tmp.x = sbt_fixcolumnwidth_len + hud_fontsize.x;
855 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
858 tmp.x = sbt_field_size[i] + hud_fontsize.x;
859 if(sbt_field_icon0 != "")
860 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);
861 if(sbt_field_icon1 != "")
862 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);
863 if(sbt_field_icon2 != "")
864 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);
867 if(sbt_field[i] == SP_SEPARATOR)
869 pos.x = item_pos.x + panel_size.x - hud_fontsize.x * 0.5;
870 for(i = sbt_num_fields-1; i > 0; --i)
872 field = sbt_field[i];
873 if(field == SP_SEPARATOR)
876 if(is_spec && field != SP_NAME && field != SP_PING) {
877 pos.x -= sbt_field_size[i] + hud_fontsize.x;
881 str = Scoreboard_GetField(pl, field);
882 str = Scoreboard_FixColumnWidth(i, str);
884 if(field == SP_NAME) {
885 tmp.x = sbt_fixcolumnwidth_len; // left or right aligned? let's put it right...
886 drawcolorcodedstring(pos - tmp, str, hud_fontsize, fg_alpha, DRAWFLAG_NORMAL);
888 tmp.x = sbt_fixcolumnwidth_len;
889 drawstring(pos - tmp, str, hud_fontsize, sbt_field_rgb, fg_alpha, DRAWFLAG_NORMAL);
892 tmp.x = sbt_field_size[i];
893 if(sbt_field_icon0 != "")
894 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);
895 if(sbt_field_icon1 != "")
896 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);
897 if(sbt_field_icon2 != "")
898 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);
899 pos.x -= sbt_field_size[i] + hud_fontsize.x;
904 drawfill(h_pos, h_size, '0 0 0', 0.5 * panel_fg_alpha, DRAWFLAG_NORMAL);
907 vector Scoreboard_DrawOthers(vector item_pos, vector rgb, int this_team, entity ignored_pl, entity pl, int pl_number)
910 vector h_pos = item_pos;
911 vector h_size = eX * panel_size.x + eY * hud_fontsize.y * 1.25;
913 bool complete = (this_team == NUM_SPECTATOR);
916 if((sbt_highlight) && (!(pl_number % 2)))
917 drawfill(h_pos, h_size, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
919 vector pos = item_pos;
920 pos.x += hud_fontsize.x * 0.5;
921 pos.y += (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
923 float width_limit = item_pos.x + panel_size.x - hud_fontsize.x;
925 width_limit -= stringwidth("...", false, hud_fontsize);
926 float namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
927 static float max_name_width = 0;
930 float min_fieldsize = 0;
931 float fieldpadding = hud_fontsize.x * 0.25;
932 if(this_team == NUM_SPECTATOR)
934 if(autocvar_hud_panel_scoreboard_spectators_showping)
935 min_fieldsize = stringwidth("999", false, hud_fontsize);
937 else if(autocvar_hud_panel_scoreboard_others_showscore)
938 min_fieldsize = stringwidth("99", false, hud_fontsize);
939 for(i = 0; pl; pl = pl.sort_next)
941 if(pl.team != this_team)
947 if(this_team == NUM_SPECTATOR)
949 if(autocvar_hud_panel_scoreboard_spectators_showping)
950 field = Scoreboard_GetField(pl, SP_PING);
952 else if(autocvar_hud_panel_scoreboard_others_showscore)
953 field = ftos(pl.(scores(ps_primary)));
955 string str = textShortenToWidth(entcs_GetName(pl.sv_entnum), namesize, hud_fontsize, stringwidth_colors);
956 float column_width = stringwidth(str, true, hud_fontsize);
957 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
959 if(column_width > max_name_width)
960 max_name_width = column_width;
961 column_width = max_name_width;
965 fieldsize = stringwidth(field, false, hud_fontsize);
966 column_width += hud_fontsize.x * 0.25 + max(fieldsize, min_fieldsize) + 2 * fieldpadding;
969 if(pos.x + column_width > width_limit)
974 drawstring(pos, "...", hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
979 pos.x = item_pos.x + hud_fontsize.x * 0.5;
980 pos.y += hud_fontsize.y * 1.25;
984 vector name_pos = pos;
985 if((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned)
986 name_pos.x += max(fieldsize, min_fieldsize) + 2 * fieldpadding + hud_fontsize.x * 0.25;
987 drawcolorcodedstring(name_pos, str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
990 h_size.x = max(fieldsize, min_fieldsize) + 2 * fieldpadding;
991 h_size.y = hud_fontsize.y;
992 vector field_pos = pos;
993 if(!((this_team == NUM_SPECTATOR) && autocvar_hud_panel_scoreboard_spectators_aligned))
994 field_pos.x += column_width - h_size.x;
996 drawfill(field_pos, h_size, '1 1 1', sbt_highlight_alpha, DRAWFLAG_NORMAL);
997 field_pos.x += fieldpadding + (max(fieldsize, min_fieldsize) - fieldsize) * 0.5;
998 drawstring(field_pos, field, hud_fontsize, sbt_field_rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1000 pos.x += column_width;
1001 pos.x += hud_fontsize.x;
1003 return eX * item_pos.x + eY * (item_pos.y + i * hud_fontsize.y * 1.25);
1006 vector Scoreboard_MakeTable(vector pos, entity tm, vector rgb, vector bg_size)
1008 int max_players = 999;
1009 if(autocvar_hud_panel_scoreboard_maxheight > 0)
1011 max_players = autocvar_hud_panel_scoreboard_maxheight * vid_conheight;
1013 max_players = (max_players - hud_fontsize.y * 1.25 - panel_bg_padding * 2) / 2;
1014 max_players = floor(max_players / (hud_fontsize.y * 1.25));
1015 if(max_players <= 1)
1017 if(max_players == tm.team_size)
1022 entity me = playerslots[current_player];
1024 panel_size.y = 1.25 * hud_fontsize.y * (1 + bound(1, tm.team_size, max_players));
1025 panel_size.y += panel_bg_padding * 2;
1028 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1029 if(panel.current_panel_bg != "0")
1030 end_pos.y += panel_bg_border * 2;
1032 if(panel_bg_padding)
1034 panel_pos += '1 1 0' * panel_bg_padding;
1035 panel_size -= '2 2 0' * panel_bg_padding;
1039 vector tmp = eX * panel_size.x + eY * 1.25 * hud_fontsize.y;
1043 drawpic(pos, "gfx/scoreboard/scoreboard_tableheader", tmp, rgb + '0.5 0.5 0.5', sbt_bg_alpha, DRAWFLAG_NORMAL);
1045 pos.y += 1.25 * hud_fontsize.y;
1048 tmp.y = panel_size.y - 1.25 * hud_fontsize.y;
1050 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1053 // print header row and highlight columns
1054 pos = Scoreboard_DrawHeader(panel_pos, rgb, (max_players < tm.team_size));
1056 // fill the table and draw the rows
1057 bool is_self = false;
1058 bool self_shown = false;
1060 for(pl = players.sort_next; pl; pl = pl.sort_next)
1062 if(pl.team != tm.team)
1064 if(i == max_players - 2 && pl != me)
1066 if(!self_shown && me.team == tm.team)
1068 Scoreboard_DrawItem(pos, rgb, me, true, i);
1070 pos.y += 1.25 * hud_fontsize.y;
1074 if(i >= max_players - 1)
1076 pos = Scoreboard_DrawOthers(pos, rgb, tm.team, (self_shown ? me : NULL), pl, i);
1079 is_self = (pl.sv_entnum == current_player);
1080 Scoreboard_DrawItem(pos, rgb, pl, is_self, i);
1083 pos.y += 1.25 * hud_fontsize.y;
1087 panel_size.x += panel_bg_padding * 2; // restore initial width
1091 bool Scoreboard_WouldDraw()
1093 if (MUTATOR_CALLHOOK(DrawScoreboard))
1095 else if (QuickMenu_IsOpened())
1097 else if (HUD_Radar_Clickable())
1099 else if (scoreboard_showscores)
1101 else if (intermission == 1)
1103 else if (intermission == 2)
1105 else if (spectatee_status != -1 && STAT(HEALTH) <= 0 && autocvar_cl_deathscoreboard && gametype != MAPINFO_TYPE_CTS && !active_minigame)
1107 else if (scoreboard_showscores_force)
1112 float average_accuracy;
1113 vector Scoreboard_AccuracyStats_Draw(vector pos, vector rgb, vector bg_size)
1115 WepSet weapons_stat = WepSet_GetFromStat();
1116 WepSet weapons_inmap = WepSet_GetFromStat_InMap();
1117 int disownedcnt = 0;
1119 FOREACH(Weapons, it != WEP_Null, {
1120 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1122 WepSet set = it.m_wepset;
1123 if (weapon_stats < 0)
1125 if (!(weapons_stat & set) && (it.spawnflags & WEP_FLAG_HIDDEN || it.spawnflags & WEP_FLAG_MUTATORBLOCKED))
1127 else if (!(weapons_stat & set || weapons_inmap & set))
1132 int weapon_cnt = (Weapons_COUNT - 1) - disownedcnt - nHidden;
1133 if (weapon_cnt <= 0) return pos;
1136 if (autocvar_hud_panel_scoreboard_accuracy_doublerows && weapon_cnt >= floor((Weapons_COUNT - nHidden - 1) * 0.5))
1138 int columnns = ceil(weapon_cnt / rows);
1140 float weapon_height = 29;
1141 float height = hud_fontsize.y + weapon_height;
1143 drawstring(pos + eX * panel_bg_padding, sprintf(_("Accuracy stats (average %d%%)"), average_accuracy), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1144 pos.y += 1.25 * hud_fontsize.y;
1145 if(panel.current_panel_bg != "0")
1146 pos.y += panel_bg_border;
1149 panel_size.y = height * rows;
1150 panel_size.y += panel_bg_padding * 2;
1153 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1154 if(panel.current_panel_bg != "0")
1155 end_pos.y += panel_bg_border * 2;
1157 if(panel_bg_padding)
1159 panel_pos += '1 1 0' * panel_bg_padding;
1160 panel_size -= '2 2 0' * panel_bg_padding;
1164 vector tmp = panel_size;
1166 float weapon_width = tmp.x / columnns / rows;
1169 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1173 // column highlighting
1174 for (int i = 0; i < columnns; ++i)
1176 drawfill(pos + eX * weapon_width * rows * i, eY * height * rows + eX * weapon_width * rows, '0 0 0', sbt_highlight_alpha, DRAWFLAG_NORMAL);
1179 for (int i = 0; i < rows; ++i)
1180 drawfill(pos + eY * weapon_height + eY * height * i, eX * tmp.x + eY * hud_fontsize.y, rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1183 average_accuracy = 0;
1184 int weapons_with_stats = 0;
1186 pos.x += weapon_width / 2;
1188 if (autocvar_hud_panel_scoreboard_accuracy_nocolors)
1191 Accuracy_LoadColors();
1193 float oldposx = pos.x;
1197 FOREACH(Weapons, it != WEP_Null, {
1198 int weapon_stats = weapon_accuracy[i - WEP_FIRST];
1200 WepSet set = it.m_wepset;
1201 if (weapon_stats < 0 && !(weapons_stat & set || weapons_inmap & set))
1205 if (weapon_stats >= 0)
1206 weapon_alpha = sbt_fg_alpha;
1208 weapon_alpha = 0.2 * sbt_fg_alpha;
1211 drawpic_aspect_skin(tmpos, it.model2, eX * weapon_width + eY * weapon_height, '1 1 1', weapon_alpha, DRAWFLAG_NORMAL);
1213 if (weapon_stats >= 0) {
1214 weapons_with_stats += 1;
1215 average_accuracy += weapon_stats; // store sum of all accuracies in average_accuracy
1218 s = sprintf("%d%%", weapon_stats * 100);
1221 padding = (weapon_width - stringwidth(s, false, hud_fontsize)) / 2; // center the accuracy value
1223 if(!autocvar_hud_panel_scoreboard_accuracy_nocolors)
1224 rgb = Accuracy_GetColor(weapon_stats);
1226 drawstring(tmpos + eX * padding + eY * weapon_height, s, hud_fontsize, rgb, sbt_fg_alpha, DRAWFLAG_NORMAL);
1228 tmpos.x += weapon_width * rows;
1229 pos.x += weapon_width * rows;
1230 if (rows == 2 && column == columnns - 1) {
1238 if (weapons_with_stats)
1239 average_accuracy = floor((average_accuracy * 100 / weapons_with_stats) + 0.5);
1241 panel_size.x += panel_bg_padding * 2; // restore initial width
1245 vector MapStats_DrawKeyValue(vector pos, string key, string value) {
1247 pos.x += hud_fontsize.x * 0.25;
1248 drawstring(pos, key, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1249 pos.x = panel_pos.x + panel_size.x - stringwidth(value, false, hud_fontsize) - hud_fontsize.x * 0.25;
1250 drawstring(pos, value, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1252 pos.y += hud_fontsize.y;
1257 vector Scoreboard_MapStats_Draw(vector pos, vector rgb, vector bg_size) {
1258 float stat_secrets_found, stat_secrets_total;
1259 float stat_monsters_killed, stat_monsters_total;
1263 // get monster stats
1264 stat_monsters_killed = STAT(MONSTERS_KILLED);
1265 stat_monsters_total = STAT(MONSTERS_TOTAL);
1267 // get secrets stats
1268 stat_secrets_found = STAT(SECRETS_FOUND);
1269 stat_secrets_total = STAT(SECRETS_TOTAL);
1271 // get number of rows
1272 if(stat_secrets_total)
1274 if(stat_monsters_total)
1277 // if no rows, return
1281 // draw table header
1282 drawstring(pos + eX * panel_bg_padding, _("Map stats:"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1283 pos.y += 1.25 * hud_fontsize.y;
1284 if(panel.current_panel_bg != "0")
1285 pos.y += panel_bg_border;
1288 panel_size.y = hud_fontsize.y * rows;
1289 panel_size.y += panel_bg_padding * 2;
1292 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1293 if(panel.current_panel_bg != "0")
1294 end_pos.y += panel_bg_border * 2;
1296 if(panel_bg_padding)
1298 panel_pos += '1 1 0' * panel_bg_padding;
1299 panel_size -= '2 2 0' * panel_bg_padding;
1303 vector tmp = panel_size;
1306 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, tmp, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1309 if(stat_monsters_total)
1311 val = sprintf("%d/%d", stat_monsters_killed, stat_monsters_total);
1312 pos = MapStats_DrawKeyValue(pos, _("Monsters killed:"), val);
1316 if(stat_secrets_total)
1318 val = sprintf("%d/%d", stat_secrets_found, stat_secrets_total);
1319 pos = MapStats_DrawKeyValue(pos, _("Secrets found:"), val);
1322 panel_size.x += panel_bg_padding * 2; // restore initial width
1327 vector Scoreboard_Rankings_Draw(vector pos, entity pl, vector rgb, vector bg_size)
1330 RANKINGS_RECEIVED_CNT = 0;
1331 for (i=RANKINGS_CNT-1; i>=0; --i)
1333 ++RANKINGS_RECEIVED_CNT;
1335 if (RANKINGS_RECEIVED_CNT == 0)
1338 vector hl_rgb = rgb + '0.5 0.5 0.5';
1340 pos.y += hud_fontsize.y;
1341 drawstring(pos + eX * panel_bg_padding, _("Rankings"), hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1342 pos.y += 1.25 * hud_fontsize.y;
1343 if(panel.current_panel_bg != "0")
1344 pos.y += panel_bg_border;
1349 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1351 float f = stringwidth(grecordholder[i], true, hud_fontsize);
1356 if(namesize > autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x)
1358 namesize = autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x;
1362 float ranksize = 3 * hud_fontsize.x;
1363 float timesize = 5 * hud_fontsize.x;
1364 vector columnsize = eX * (ranksize + timesize + namesize + hud_fontsize.x) + eY * 1.25 * hud_fontsize.y;
1365 int columns = max(1, floor((panel_size.x - 2 * panel_bg_padding) / columnsize.x));
1366 columns = min(columns, RANKINGS_RECEIVED_CNT);
1368 // expand name column to fill the entire row
1369 float available_space = (panel_size.x - 2 * panel_bg_padding - columnsize.x * columns) / columns;
1370 namesize += available_space;
1371 columnsize.x += available_space;
1373 panel_size.y = ceil(RANKINGS_RECEIVED_CNT / columns) * 1.25 * hud_fontsize.y;
1374 panel_size.y += panel_bg_padding * 2;
1378 vector end_pos = panel_pos + eY * (panel_size.y + hud_fontsize.y);
1379 if(panel.current_panel_bg != "0")
1380 end_pos.y += panel_bg_border * 2;
1382 if(panel_bg_padding)
1384 panel_pos += '1 1 0' * panel_bg_padding;
1385 panel_size -= '2 2 0' * panel_bg_padding;
1391 drawpic_tiled(pos, "gfx/scoreboard/scoreboard_bg", bg_size, panel_size, rgb, sbt_bg_alpha, DRAWFLAG_NORMAL);
1393 vector text_ofs = eX * 0.5 * hud_fontsize.x + eY * (1.25 - 1) / 2 * hud_fontsize.y; // center text vertically
1395 int column = 0, j = 0;
1396 for(i = 0; i < RANKINGS_RECEIVED_CNT; ++i)
1403 if(grecordholder[i] == entcs_GetName(player_localnum))
1404 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha_self, DRAWFLAG_NORMAL);
1405 else if(!((j + column) & 1) && sbt_highlight)
1406 drawfill(pos, columnsize, hl_rgb, sbt_highlight_alpha, DRAWFLAG_NORMAL);
1408 str = count_ordinal(i+1);
1409 drawstring(pos + text_ofs, str, hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1410 drawstring(pos + text_ofs + eX * ranksize, TIME_ENCODED_TOSTRING(t), hud_fontsize, '1 1 1', sbt_fg_alpha, DRAWFLAG_NORMAL);
1411 str = grecordholder[i];
1413 str = textShortenToWidth(str, namesize, hud_fontsize, stringwidth_colors);
1414 drawcolorcodedstring(pos + text_ofs + eX * (ranksize + timesize), str, hud_fontsize, sbt_fg_alpha, DRAWFLAG_NORMAL);
1416 pos.y += 1.25 * hud_fontsize.y;
1418 if(j >= ceil(RANKINGS_RECEIVED_CNT / columns))
1422 pos.x += panel_size.x / columns;
1423 pos.y = panel_pos.y;
1427 panel_size.x += panel_bg_padding * 2; // restore initial width
1431 void Scoreboard_Draw()
1433 if(!autocvar__hud_configure)
1435 if(!hud_draw_maximized) return;
1437 // frametime checks allow to toggle the scoreboard even when the game is paused
1438 if(scoreboard_active) {
1439 if(hud_configure_menu_open == 1)
1440 scoreboard_fade_alpha = 1;
1441 float scoreboard_fadeinspeed = autocvar_hud_panel_scoreboard_fadeinspeed;
1442 if (scoreboard_fadeinspeed && frametime)
1443 scoreboard_fade_alpha = min(1, scoreboard_fade_alpha + frametime * scoreboard_fadeinspeed);
1445 scoreboard_fade_alpha = 1;
1446 if(hud_fontsize_str != autocvar_hud_fontsize)
1448 hud_fontsize = HUD_GetFontsize("hud_fontsize");
1449 Scoreboard_initFieldSizes();
1450 if(hud_fontsize_str)
1451 strunzone(hud_fontsize_str);
1452 hud_fontsize_str = strzone(autocvar_hud_fontsize);
1456 float scoreboard_fadeoutspeed = autocvar_hud_panel_scoreboard_fadeoutspeed;
1457 if (scoreboard_fadeoutspeed && frametime)
1458 scoreboard_fade_alpha = max(0, scoreboard_fade_alpha - frametime * scoreboard_fadeoutspeed);
1460 scoreboard_fade_alpha = 0;
1463 if (!scoreboard_fade_alpha)
1467 scoreboard_fade_alpha = 0;
1469 if (autocvar_hud_panel_scoreboard_dynamichud)
1472 HUD_Scale_Disable();
1474 if(scoreboard_fade_alpha <= 0)
1476 panel_fade_alpha *= scoreboard_fade_alpha;
1477 HUD_Panel_LoadCvars();
1479 sbt_bg_alpha = autocvar_hud_panel_scoreboard_table_bg_alpha * panel_fg_alpha;
1480 sbt_highlight = autocvar_hud_panel_scoreboard_table_highlight;
1481 sbt_highlight_alpha = autocvar_hud_panel_scoreboard_table_highlight_alpha * panel_fg_alpha;
1482 sbt_highlight_alpha_self = autocvar_hud_panel_scoreboard_table_highlight_alpha_self * panel_fg_alpha;
1483 sbt_fg_alpha = autocvar_hud_panel_scoreboard_table_fg_alpha * panel_fg_alpha;
1484 sbt_fg_alpha_self = autocvar_hud_panel_scoreboard_table_fg_alpha_self * panel_fg_alpha;
1486 // don't overlap with con_notify
1487 if(!autocvar__hud_configure)
1488 panel_pos.y = max((autocvar_con_notify * autocvar_con_notifysize), panel_pos.y);
1490 float excess = max(0, max_namesize - autocvar_hud_panel_scoreboard_namesize * hud_fontsize.x);
1491 float fixed_scoreboard_width = bound(vid_conwidth * autocvar_hud_panel_scoreboard_minwidth, vid_conwidth - excess, vid_conwidth * 0.93);
1492 panel_pos.x = 0.5 * (vid_conwidth - fixed_scoreboard_width);
1493 panel_size.x = fixed_scoreboard_width;
1495 Scoreboard_UpdatePlayerTeams();
1497 vector pos = panel_pos;
1502 vector sb_heading_fontsize;
1503 sb_heading_fontsize = hud_fontsize * 2;
1504 draw_beginBoldFont();
1505 drawstring(pos + eX * panel_bg_padding, _("Scoreboard"), sb_heading_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1508 pos.y += sb_heading_fontsize.y;
1509 if(panel.current_panel_bg != "0")
1510 pos.y += panel_bg_border;
1512 // Draw the scoreboard
1513 float scale = autocvar_hud_panel_scoreboard_table_bg_scale;
1516 vector bg_size = draw_getimagesize("gfx/scoreboard/scoreboard_bg") * scale;
1520 vector panel_bg_color_save = panel_bg_color;
1521 vector team_score_baseoffset = eY * hud_fontsize.y - eX * hud_fontsize.x * 0.5;
1522 if(panel.current_panel_bg != "0")
1523 team_score_baseoffset.x -= panel_bg_border;
1524 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1526 if(tm.team == NUM_SPECTATOR)
1531 draw_beginBoldFont();
1532 vector rgb = Team_ColorRGB(tm.team);
1533 str = ftos(tm.(teamscores(ts_primary)));
1534 drawstring(pos + team_score_baseoffset - eX * stringwidth(str, false, hud_fontsize * 1.5), str, hud_fontsize * 1.5, rgb, panel_fg_alpha, DRAWFLAG_NORMAL);
1536 if(ts_primary != ts_secondary)
1538 str = ftos(tm.(teamscores(ts_secondary)));
1539 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);
1542 if(autocvar_hud_panel_scoreboard_bg_teams_color_team > 0)
1543 panel_bg_color = rgb * autocvar_hud_panel_scoreboard_bg_teams_color_team;
1544 else if(panel_bg_color_team > 0)
1545 panel_bg_color = rgb * panel_bg_color_team;
1547 panel_bg_color = rgb;
1548 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1550 panel_bg_color = panel_bg_color_save;
1554 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1555 if(tm.team != NUM_SPECTATOR)
1557 // display it anyway
1558 pos = Scoreboard_MakeTable(pos, tm, panel_bg_color, bg_size);
1561 if(gametype == MAPINFO_TYPE_CTS || gametype == MAPINFO_TYPE_RACE) {
1562 if(race_speedaward) {
1563 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);
1564 pos.y += 1.25 * hud_fontsize.y;
1566 if(race_speedaward_alltimebest) {
1567 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);
1568 pos.y += 1.25 * hud_fontsize.y;
1570 pos = Scoreboard_Rankings_Draw(pos, playerslots[player_localnum], panel_bg_color, bg_size);
1572 else if (autocvar_hud_panel_scoreboard_accuracy && !warmup_stage && gametype != MAPINFO_TYPE_NEXBALL)
1573 pos = Scoreboard_AccuracyStats_Draw(pos, panel_bg_color, bg_size);
1575 pos = Scoreboard_MapStats_Draw(pos, panel_bg_color, bg_size);
1578 for(pl = players.sort_next; pl; pl = pl.sort_next)
1580 if(pl.team == NUM_SPECTATOR)
1582 for(tm = teams.sort_next; tm; tm = tm.sort_next)
1583 if(tm.team == NUM_SPECTATOR)
1585 str = sprintf("%s (%d)", _("Spectators"), tm.team_size);
1586 draw_beginBoldFont();
1587 drawstring(pos, str, hud_fontsize, '1 1 1', panel_fg_alpha, DRAWFLAG_NORMAL);
1589 pos.y += 1.25 * hud_fontsize.y;
1591 pos = Scoreboard_DrawOthers(pos, '0 0 0', pl.team, NULL, pl, 0);
1592 pos.y += 1.25 * hud_fontsize.y;
1598 // Print info string
1600 str = sprintf(_("playing ^3%s^7 on ^2%s^7"), MapInfo_Type_ToText(gametype), shortmapname);
1601 tl = STAT(TIMELIMIT);
1602 fl = STAT(FRAGLIMIT);
1603 ll = STAT(LEADLIMIT);
1604 if(gametype == MAPINFO_TYPE_LMS)
1607 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1612 str = strcat(str, sprintf(_(" for up to ^1%1.0f minutes^7"), tl));
1616 str = strcat(str, _(" or"));
1619 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), fl),
1620 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1621 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1622 TranslateScoresLabel(teamscores_label(ts_primary))));
1626 str = strcat(str, sprintf(_(" until ^3%s %s^7"), ScoreString(scores_flags(ps_primary), fl),
1627 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1628 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1629 TranslateScoresLabel(scores_label(ps_primary))));
1634 if(tl > 0 || fl > 0)
1635 str = strcat(str, _(" or"));
1638 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(teamscores_flags(ts_primary), ll),
1639 (teamscores_label(ts_primary) == "score") ? CTX(_("SCO^points")) :
1640 (teamscores_label(ts_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1641 TranslateScoresLabel(teamscores_label(ts_primary))));
1645 str = strcat(str, sprintf(_(" until a lead of ^3%s %s^7"), ScoreString(scores_flags(ps_primary), ll),
1646 (scores_label(ps_primary) == "score") ? CTX(_("SCO^points")) :
1647 (scores_label(ps_primary) == "fastest") ? CTX(_("SCO^is beaten")) :
1648 TranslateScoresLabel(scores_label(ps_primary))));
1653 pos.y += 1.2 * hud_fontsize.y;
1654 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1656 // print information about respawn status
1657 float respawn_time = STAT(RESPAWN_TIME);
1661 if(respawn_time < 0)
1663 // a negative number means we are awaiting respawn, time value is still the same
1664 respawn_time *= -1; // remove mark now that we checked it
1666 if(respawn_time < time) // it happens for a few frames when server is respawning the player
1667 str = ""; // draw an empty string to not change suddenly scoreboard_bottom
1669 str = sprintf(_("^1Respawning in ^3%s^1..."),
1670 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1671 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1673 count_seconds(ceil(respawn_time - time))
1677 else if(time < respawn_time)
1679 str = sprintf(_("You are dead, wait ^3%s^7 before respawning"),
1680 (autocvar_hud_panel_scoreboard_respawntime_decimals ?
1681 count_seconds_decs(respawn_time - time, autocvar_hud_panel_scoreboard_respawntime_decimals)
1683 count_seconds(ceil(respawn_time - time))
1687 else if(time >= respawn_time)
1688 str = sprintf(_("You are dead, press ^2%s^7 to respawn"), getcommandkey("jump", "+jump"));
1690 pos.y += 1.2 * hud_fontsize.y;
1691 drawcolorcodedstring(pos + '0.5 0 0' * (panel_size.x - stringwidth(str, true, hud_fontsize)), str, hud_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL);
1694 scoreboard_bottom = pos.y + 2 * hud_fontsize.y;